@solidjs/router 2.0.0-next.31 → 2.0.0-next.32
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/dist/data/query.js +63 -25
- package/dist/index.js +106 -32
- package/dist/routers/factory.jsx +63 -15
- package/dist/routing.js +2 -1
- package/dist/utils.js +3 -2
- package/package.json +5 -5
package/dist/data/query.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createSignal, getObserver, getOwner,
|
|
1
|
+
import { createSignal, getObserver, getOwner, isHydratable, onCleanup, untrack } from "solid-js";
|
|
2
2
|
// Everything server-function-shaped comes off the CORE entry: detection
|
|
3
3
|
// (isServerFunction/getServerFunctionMetadata, registered-symbol reads) and
|
|
4
4
|
// the late-bound RPC seam (getServerFunctionRPC). The transport itself —
|
|
@@ -9,7 +9,7 @@ import { createSignal, getObserver, getOwner, onCleanup, sharedConfig, untrack }
|
|
|
9
9
|
// into the seam when a `'use server'` reference is created (compiled
|
|
10
10
|
// output, module scope), so by the time a server function can reach
|
|
11
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";
|
|
12
|
+
import { getHydrationWriter, getRequestEvent, getServerFunctionMetadata, getServerFunctionRPC, isResponseEnvelope, isServer, isServerFunction, REVALIDATE_HEADER, takeHydrationValue } from "@solidjs/web";
|
|
13
13
|
// The redirect carrier's name and decoder are the exception: two pure,
|
|
14
14
|
// dependency-free bindings off a `sideEffects: false` entry, so they
|
|
15
15
|
// tree-shake to a few hundred bytes without dragging the codec in (the same
|
|
@@ -19,10 +19,34 @@ import { useRouter, getIntent, getInPreloadFn } from "../routing.js";
|
|
|
19
19
|
const LocationHeader = "Location";
|
|
20
20
|
const PRELOAD_TIMEOUT = 5000;
|
|
21
21
|
const CACHE_TIMEOUT = 180000;
|
|
22
|
-
|
|
22
|
+
const responseBodyHeaders = /* #__PURE__ */ new Set([
|
|
23
|
+
"content-type",
|
|
24
|
+
"content-length",
|
|
25
|
+
"content-encoding",
|
|
26
|
+
"transfer-encoding",
|
|
27
|
+
"content-disposition",
|
|
28
|
+
"content-language",
|
|
29
|
+
"content-location",
|
|
30
|
+
"content-range",
|
|
31
|
+
"accept-ranges",
|
|
32
|
+
"etag",
|
|
33
|
+
"last-modified"
|
|
34
|
+
]);
|
|
35
|
+
const forwardedCookies = /* #__PURE__ */ new WeakMap();
|
|
36
|
+
// When this client booted. Flight-registry entries (takeHydrationValue)
|
|
23
37
|
// hold values the server computed while rendering THIS page, so their age is
|
|
24
38
|
// anchored here — not at whenever a late query() call happens to consume one.
|
|
25
39
|
const bootTime = Date.now();
|
|
40
|
+
// Every hydratable read ships its entry: the writer keeps the first write
|
|
41
|
+
// per key, so a read skipped in a NoHydration zone never hides the entry
|
|
42
|
+
// from an island that reads the same key later.
|
|
43
|
+
function serializeForHydration(key, res) {
|
|
44
|
+
const writer = getHydrationWriter();
|
|
45
|
+
if (!writer || !writer.async || !isHydratable())
|
|
46
|
+
return;
|
|
47
|
+
const e = getRequestEvent();
|
|
48
|
+
(!e || !e.serverOnly) && writer.write(key, res);
|
|
49
|
+
}
|
|
26
50
|
let cacheMap = new Map();
|
|
27
51
|
// cleanup forward/back cache
|
|
28
52
|
if (!isServer) {
|
|
@@ -160,6 +184,7 @@ export function query(fn, name) {
|
|
|
160
184
|
!isServer && intent === "navigate" && cached[4][1](cached[0]); // update version
|
|
161
185
|
}
|
|
162
186
|
inPreloadFn && "then" in res && res.catch(() => { });
|
|
187
|
+
isServer && serializeForHydration(key, res);
|
|
163
188
|
return res;
|
|
164
189
|
}
|
|
165
190
|
let res;
|
|
@@ -176,13 +201,17 @@ export function query(fn, name) {
|
|
|
176
201
|
// active navigation (or its preload) accepts the payload only while a
|
|
177
202
|
// real preload of the same age would still satisfy it — anything older
|
|
178
203
|
// refetches instead of serving a minutes-old value on a fresh navigation.
|
|
179
|
-
if (!isServer &&
|
|
180
|
-
|
|
181
|
-
|
|
204
|
+
if (!isServer &&
|
|
205
|
+
(!intent || now - bootTime < (intent === "native" ? CACHE_TIMEOUT : PRELOAD_TIMEOUT))) {
|
|
206
|
+
const entry = takeHydrationValue(key); // hydrating
|
|
207
|
+
if (entry) {
|
|
182
208
|
adopted = true;
|
|
183
|
-
res =
|
|
184
|
-
|
|
185
|
-
|
|
209
|
+
res =
|
|
210
|
+
entry.status === "pending"
|
|
211
|
+
? entry.promise
|
|
212
|
+
: entry.status === "resolved"
|
|
213
|
+
? Promise.resolve(entry.value)
|
|
214
|
+
: Promise.reject(entry.error);
|
|
186
215
|
}
|
|
187
216
|
// A payload too old for this intent stays in the registry: it is
|
|
188
217
|
// inert while the refetched entry below lives, but a later claim
|
|
@@ -198,6 +227,7 @@ export function query(fn, name) {
|
|
|
198
227
|
// it would for a preload performed when the server fetched the data.
|
|
199
228
|
const stamp = adopted ? bootTime : now;
|
|
200
229
|
if (cached) {
|
|
230
|
+
isServer && forwardedCookies.delete(cached);
|
|
201
231
|
cached[0] = stamp;
|
|
202
232
|
cached[1] = res;
|
|
203
233
|
cached[3] = intent;
|
|
@@ -225,22 +255,18 @@ export function query(fn, name) {
|
|
|
225
255
|
if (intent !== "preload")
|
|
226
256
|
res = settle(res);
|
|
227
257
|
inPreloadFn && "then" in res && res.catch(() => { });
|
|
228
|
-
|
|
229
|
-
if (isServer &&
|
|
230
|
-
sharedConfig.context &&
|
|
231
|
-
sharedConfig.context.async &&
|
|
232
|
-
!sharedConfig.context.noHydrate) {
|
|
233
|
-
const e = getRequestEvent();
|
|
234
|
-
(!e || !e.serverOnly) && sharedConfig.context.serialize(key, res);
|
|
235
|
-
}
|
|
258
|
+
isServer && serializeForHydration(key, res);
|
|
236
259
|
return res;
|
|
237
260
|
// a settled read: the response handling applied to the value or the promise
|
|
238
261
|
function settle(v) {
|
|
262
|
+
let cookies = isServer ? forwardedCookies.get(cached) : undefined;
|
|
263
|
+
if (isServer && !cookies)
|
|
264
|
+
forwardedCookies.set(cached, (cookies = new WeakSet()));
|
|
239
265
|
return "then" in v
|
|
240
|
-
? v.then(handleResponse(false), handleResponse(true))
|
|
241
|
-
: handleResponse(false)(v);
|
|
266
|
+
? v.then(handleResponse(false, cookies), handleResponse(true, cookies))
|
|
267
|
+
: handleResponse(false, cookies)(v);
|
|
242
268
|
}
|
|
243
|
-
function handleResponse(error) {
|
|
269
|
+
function handleResponse(error, cookies) {
|
|
244
270
|
return async (v) => {
|
|
245
271
|
let enveloped;
|
|
246
272
|
let hasEnveloped = false;
|
|
@@ -253,11 +279,22 @@ export function query(fn, name) {
|
|
|
253
279
|
if (v instanceof Response) {
|
|
254
280
|
const e = getRequestEvent();
|
|
255
281
|
if (e) {
|
|
282
|
+
const headers = e.response.headers;
|
|
283
|
+
const skipCookies = cookies?.has(headers);
|
|
256
284
|
for (const [key, value] of v.headers) {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
285
|
+
// Keep metadata for the query's body off the document response.
|
|
286
|
+
if (responseBodyHeaders.has(key))
|
|
287
|
+
continue;
|
|
288
|
+
if (key === "set-cookie") {
|
|
289
|
+
if (!skipCookies)
|
|
290
|
+
headers.append(key, value);
|
|
291
|
+
}
|
|
292
|
+
else if (headers.get(key) !== value)
|
|
293
|
+
headers.set(key, value);
|
|
294
|
+
}
|
|
295
|
+
// Each cached result contributes its cookies once per document.
|
|
296
|
+
if (!skipCookies && v.headers.has("set-cookie")) {
|
|
297
|
+
cookies?.add(headers);
|
|
261
298
|
}
|
|
262
299
|
}
|
|
263
300
|
let url = v.headers.get(LocationHeader);
|
|
@@ -346,6 +383,7 @@ query.set = (key, value) => {
|
|
|
346
383
|
const now = Date.now();
|
|
347
384
|
let cached = cache.get(key);
|
|
348
385
|
if (cached) {
|
|
386
|
+
isServer && forwardedCookies.delete(cached);
|
|
349
387
|
cached[0] = now;
|
|
350
388
|
cached[1] = Promise.resolve(value);
|
|
351
389
|
cached[2] = value;
|
|
@@ -399,7 +437,7 @@ export function hashKey(args) {
|
|
|
399
437
|
.reduce((result, key) => {
|
|
400
438
|
result[key] = val[key];
|
|
401
439
|
return result;
|
|
402
|
-
},
|
|
440
|
+
}, Object.create(null))
|
|
403
441
|
: val);
|
|
404
442
|
}
|
|
405
443
|
function isPlainObject(obj) {
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { getOwner, runWithOwner, createMemo, createRenderEffect, onCleanup, untrack, createContext, createSignal, useContext, NotReadyError, DEV, isPending, latest, createComponent, createRoot, Show, createEffect,
|
|
2
|
-
import { registerElementClaim, delegateEvents, isServer, getRequestEvent, hasFlashCookie, clearFlashCookie, createComponent as createComponent$1, memo, dynamic, isServerFunction, getServerFunctionMetadata, getServerFunctionRPC, isResponseEnvelope, REVALIDATE_HEADER } from '@solidjs/web';
|
|
1
|
+
import { getOwner, runWithOwner, createMemo, createRenderEffect, onCleanup, untrack, createContext, createSignal, useContext, NotReadyError, DEV, isPending, latest, createComponent, createRoot, Show, createEffect, OBSERVE, onSettled, getObserver, isHydratable, $TRACK, action as action$1 } from 'solid-js';
|
|
2
|
+
import { registerElementClaim, delegateEvents, isServer, getRequestEvent, hasFlashCookie, clearFlashCookie, createComponent as createComponent$1, memo, dynamic, isServerFunction, getServerFunctionMetadata, getServerFunctionRPC, takeHydrationValue, isResponseEnvelope, REVALIDATE_HEADER, getHydrationWriter } from '@solidjs/web';
|
|
3
3
|
import { REDIRECT_HEADER, decodeRedirectHeaderValue, subscribeFlightData, decodeResponsePayload, parseServerFunctionActionUrl, createServerReference } from '@solidjs/web/server-functions';
|
|
4
4
|
|
|
5
5
|
const hasSchemeRegex = /^(?:[a-z0-9]+:)?\/\//i;
|
|
@@ -45,13 +45,16 @@ function validateSearch(schema, raw) {
|
|
|
45
45
|
return outcome;
|
|
46
46
|
}
|
|
47
47
|
function extractSearchParams(url) {
|
|
48
|
-
const params =
|
|
48
|
+
const params = Object.create(null);
|
|
49
49
|
url.searchParams.forEach((value, key) => {
|
|
50
50
|
if (key in params) {
|
|
51
51
|
if (Array.isArray(params[key])) params[key].push(value);else params[key] = [params[key], value];
|
|
52
52
|
} else params[key] = value;
|
|
53
53
|
});
|
|
54
|
-
|
|
54
|
+
// Keep __proto__ as an own property on the plain object returned to callers.
|
|
55
|
+
return {
|
|
56
|
+
...params
|
|
57
|
+
};
|
|
55
58
|
}
|
|
56
59
|
function createMatcher(path, partial, matchFilters) {
|
|
57
60
|
const [pattern, splat] = path.split("/*", 2);
|
|
@@ -1118,7 +1121,8 @@ function createLocation(path, state, queryWrapper) {
|
|
|
1118
1121
|
const pathname = createMemo(() => url().pathname);
|
|
1119
1122
|
const search = createMemo(() => url().search);
|
|
1120
1123
|
const hash = createMemo(() => url().hash);
|
|
1121
|
-
|
|
1124
|
+
// Pathname and hash changes do not affect the query.
|
|
1125
|
+
const queryFn = createMemo(() => extractSearchParams(new URL(search(), mockBase)));
|
|
1122
1126
|
return {
|
|
1123
1127
|
get pathname() {
|
|
1124
1128
|
return pathname();
|
|
@@ -2211,7 +2215,6 @@ function describeNavigation(match, next, written, from) {
|
|
|
2211
2215
|
const t = to();
|
|
2212
2216
|
return t === undefined ? undefined : new URL(t, mockBase).pathname;
|
|
2213
2217
|
};
|
|
2214
|
-
const matches = pathname => untrack(() => match(pathname));
|
|
2215
2218
|
const ref = {
|
|
2216
2219
|
kind: "navigation",
|
|
2217
2220
|
get to() {
|
|
@@ -2219,21 +2222,62 @@ function describeNavigation(match, next, written, from) {
|
|
|
2219
2222
|
},
|
|
2220
2223
|
from,
|
|
2221
2224
|
get name() {
|
|
2222
|
-
|
|
2223
|
-
if (p === undefined) return undefined;
|
|
2224
|
-
const m = matches(p);
|
|
2225
|
-
return m.length ? m[m.length - 1].route.pattern || "/" : p;
|
|
2225
|
+
return routeName(match, pathname());
|
|
2226
2226
|
},
|
|
2227
2227
|
get params() {
|
|
2228
|
-
|
|
2229
|
-
const m = p === undefined ? [] : matches(p);
|
|
2230
|
-
return m.length ? mergeParams(m) : undefined;
|
|
2228
|
+
return routeParams(match, pathname());
|
|
2231
2229
|
}
|
|
2232
2230
|
};
|
|
2233
2231
|
if (next._navigation !== undefined && next._navigation > 1) ref.redirect = next._navigation - 1;
|
|
2234
2232
|
return ref;
|
|
2235
2233
|
}
|
|
2236
2234
|
|
|
2235
|
+
/** The parametrized route `pathname` matches — the last match's pattern, `/` for the root; the pathname itself when nothing matched. */
|
|
2236
|
+
function routeName(match, pathname) {
|
|
2237
|
+
if (pathname === undefined) return undefined;
|
|
2238
|
+
const m = untrack(() => match(pathname));
|
|
2239
|
+
return m.length ? m[m.length - 1].route.pattern || "/" : pathname;
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
/** The params the matched chain bound, merged; `undefined` when nothing matched. */
|
|
2243
|
+
function routeParams(match, pathname) {
|
|
2244
|
+
if (pathname === undefined) return undefined;
|
|
2245
|
+
const m = untrack(() => match(pathname));
|
|
2246
|
+
return m.length ? mergeParams(m) : undefined;
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
/**
|
|
2250
|
+
* The route the document arrived on, for solid's observe tier: the same
|
|
2251
|
+
* declaration as a navigation's, around the work that establishes the
|
|
2252
|
+
* router's initial match — building its context — since a fresh document has
|
|
2253
|
+
* no location write to wrap. On the client the engine opens it at the time
|
|
2254
|
+
* origin and settles it as the context is built (the first `"navigation"`
|
|
2255
|
+
* record, `initial: true`); on the server the same call names the request's
|
|
2256
|
+
* `"render"` record (`RenderEvent.route`). Every field is a getter read at
|
|
2257
|
+
* settle, as for a navigation: `to` is the location as the router then holds
|
|
2258
|
+
* it — the arrival normalized onto the base path when it came in empty, a
|
|
2259
|
+
* write the frame itself took (see the setter) — and `name`/`params` match
|
|
2260
|
+
* it then, so a lazy subtree that resolved while the server rendered names
|
|
2261
|
+
* the exact route.
|
|
2262
|
+
*/
|
|
2263
|
+
function describeInitial(match, location) {
|
|
2264
|
+
const to = () => untrack(location).value;
|
|
2265
|
+
const pathname = () => new URL(to(), mockBase).pathname;
|
|
2266
|
+
return {
|
|
2267
|
+
kind: "navigation",
|
|
2268
|
+
initial: true,
|
|
2269
|
+
get to() {
|
|
2270
|
+
return to();
|
|
2271
|
+
},
|
|
2272
|
+
get name() {
|
|
2273
|
+
return routeName(match, pathname());
|
|
2274
|
+
},
|
|
2275
|
+
get params() {
|
|
2276
|
+
return routeParams(match, pathname());
|
|
2277
|
+
}
|
|
2278
|
+
};
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2237
2281
|
/** Wraps a history adapter in the integration signal the router core consumes. Must run under a reactive owner. */
|
|
2238
2282
|
function createIntegration(history, match) {
|
|
2239
2283
|
let committing = false;
|
|
@@ -2248,7 +2292,6 @@ function createIntegration(history, match) {
|
|
|
2248
2292
|
name: "location"
|
|
2249
2293
|
});
|
|
2250
2294
|
const signal = [read, next => {
|
|
2251
|
-
if (sharedConfig.registry && !sharedConfig.done) sharedConfig.done = true;
|
|
2252
2295
|
// What the write resolved to, or undefined when there was nothing to
|
|
2253
2296
|
// write (see `resolveLocationWrite`).
|
|
2254
2297
|
let written;
|
|
@@ -2281,7 +2324,14 @@ function createIntegration(history, match) {
|
|
|
2281
2324
|
// while a navigation is pending, which is the `from` a hop wants too.
|
|
2282
2325
|
// A write the no-op rule drops is still declared: the engine settles a
|
|
2283
2326
|
// navigation whose write "did not survive the equality gate" on the spot.
|
|
2284
|
-
|
|
2327
|
+
// A write from NO location is not a navigation: it is the arrival being
|
|
2328
|
+
// normalized (`createRouterContext` moving an empty location — a hash
|
|
2329
|
+
// history with no hash, a memory history seeded with "" — onto the base
|
|
2330
|
+
// path), and the arrival is already declared by the initial frame open
|
|
2331
|
+
// around the context build (`describeInitial`), which this write then
|
|
2332
|
+
// stamps. The location is never empty again after that first write.
|
|
2333
|
+
const from = untrack(read).value;
|
|
2334
|
+
OBSERVE && from ? OBSERVE.attribution.withOrigin(describeNavigation(match, next, () => written, from), commit) : commit();
|
|
2285
2335
|
}];
|
|
2286
2336
|
history.init && onCleanup(history.init((value = history.get()) => {
|
|
2287
2337
|
if (committing) return;
|
|
@@ -2362,11 +2412,14 @@ function createRouter(config) {
|
|
|
2362
2412
|
}
|
|
2363
2413
|
const integration = isServer ? staticIntegration(props.url, config.history && config.history.utils) : createIntegration(history || browserHistory(), matchPath);
|
|
2364
2414
|
let context;
|
|
2365
|
-
const
|
|
2415
|
+
const buildContext = () => createRouterContext(integration, branches, () => context, {
|
|
2366
2416
|
base: basePath,
|
|
2367
2417
|
singleFlight: config.singleFlight,
|
|
2368
2418
|
transformUrl: config.transformUrl
|
|
2369
2419
|
});
|
|
2420
|
+
// The route the document arrived on, declared around the initial match
|
|
2421
|
+
// (see `describeInitial`); folded out of shipped bundles with `OBSERVE`.
|
|
2422
|
+
const routerState = OBSERVE ? OBSERVE.attribution.withOrigin(describeInitial(matchPath, integration.signal[0]), buildContext) : buildContext();
|
|
2370
2423
|
if (!isServer) {
|
|
2371
2424
|
setupNativeEvents({
|
|
2372
2425
|
preload: config.preloadLinks,
|
|
@@ -2604,10 +2657,22 @@ source) {
|
|
|
2604
2657
|
const LocationHeader = "Location";
|
|
2605
2658
|
const PRELOAD_TIMEOUT$1 = 5000;
|
|
2606
2659
|
const CACHE_TIMEOUT = 180000;
|
|
2607
|
-
|
|
2660
|
+
const responseBodyHeaders = /* #__PURE__ */new Set(["content-type", "content-length", "content-encoding", "transfer-encoding", "content-disposition", "content-language", "content-location", "content-range", "accept-ranges", "etag", "last-modified"]);
|
|
2661
|
+
const forwardedCookies = /* #__PURE__ */new WeakMap();
|
|
2662
|
+
// When this client booted. Flight-registry entries (takeHydrationValue)
|
|
2608
2663
|
// hold values the server computed while rendering THIS page, so their age is
|
|
2609
2664
|
// anchored here — not at whenever a late query() call happens to consume one.
|
|
2610
2665
|
const bootTime = Date.now();
|
|
2666
|
+
|
|
2667
|
+
// Every hydratable read ships its entry: the writer keeps the first write
|
|
2668
|
+
// per key, so a read skipped in a NoHydration zone never hides the entry
|
|
2669
|
+
// from an island that reads the same key later.
|
|
2670
|
+
function serializeForHydration(key, res) {
|
|
2671
|
+
const writer = getHydrationWriter();
|
|
2672
|
+
if (!writer || !writer.async || !isHydratable()) return;
|
|
2673
|
+
const e = getRequestEvent();
|
|
2674
|
+
(!e || !e.serverOnly) && writer.write(key, res);
|
|
2675
|
+
}
|
|
2611
2676
|
let cacheMap = new Map();
|
|
2612
2677
|
|
|
2613
2678
|
// cleanup forward/back cache
|
|
@@ -2736,6 +2801,7 @@ function query(fn, name) {
|
|
|
2736
2801
|
!isServer && intent === "navigate" && cached[4][1](cached[0]); // update version
|
|
2737
2802
|
}
|
|
2738
2803
|
inPreloadFn && "then" in res && res.catch(() => {});
|
|
2804
|
+
isServer && serializeForHydration(key, res);
|
|
2739
2805
|
return res;
|
|
2740
2806
|
}
|
|
2741
2807
|
let res;
|
|
@@ -2752,13 +2818,11 @@ function query(fn, name) {
|
|
|
2752
2818
|
// active navigation (or its preload) accepts the payload only while a
|
|
2753
2819
|
// real preload of the same age would still satisfy it — anything older
|
|
2754
2820
|
// refetches instead of serving a minutes-old value on a fresh navigation.
|
|
2755
|
-
if (!isServer &&
|
|
2756
|
-
const
|
|
2757
|
-
if (
|
|
2821
|
+
if (!isServer && (!intent || now - bootTime < (intent === "native" ? CACHE_TIMEOUT : PRELOAD_TIMEOUT$1))) {
|
|
2822
|
+
const entry = takeHydrationValue(key); // hydrating
|
|
2823
|
+
if (entry) {
|
|
2758
2824
|
adopted = true;
|
|
2759
|
-
res =
|
|
2760
|
-
// @ts-ignore at least until we add a delete method to sharedConfig
|
|
2761
|
-
delete globalThis._$HY.r[key];
|
|
2825
|
+
res = entry.status === "pending" ? entry.promise : entry.status === "resolved" ? Promise.resolve(entry.value) : Promise.reject(entry.error);
|
|
2762
2826
|
}
|
|
2763
2827
|
// A payload too old for this intent stays in the registry: it is
|
|
2764
2828
|
// inert while the refetched entry below lives, but a later claim
|
|
@@ -2774,6 +2838,7 @@ function query(fn, name) {
|
|
|
2774
2838
|
// it would for a preload performed when the server fetched the data.
|
|
2775
2839
|
const stamp = adopted ? bootTime : now;
|
|
2776
2840
|
if (cached) {
|
|
2841
|
+
isServer && forwardedCookies.delete(cached);
|
|
2777
2842
|
cached[0] = stamp;
|
|
2778
2843
|
cached[1] = res;
|
|
2779
2844
|
cached[3] = intent;
|
|
@@ -2794,18 +2859,16 @@ function query(fn, name) {
|
|
|
2794
2859
|
}
|
|
2795
2860
|
if (intent !== "preload") res = settle(res);
|
|
2796
2861
|
inPreloadFn && "then" in res && res.catch(() => {});
|
|
2797
|
-
|
|
2798
|
-
if (isServer && sharedConfig.context && sharedConfig.context.async && !sharedConfig.context.noHydrate) {
|
|
2799
|
-
const e = getRequestEvent();
|
|
2800
|
-
(!e || !e.serverOnly) && sharedConfig.context.serialize(key, res);
|
|
2801
|
-
}
|
|
2862
|
+
isServer && serializeForHydration(key, res);
|
|
2802
2863
|
return res;
|
|
2803
2864
|
|
|
2804
2865
|
// a settled read: the response handling applied to the value or the promise
|
|
2805
2866
|
function settle(v) {
|
|
2806
|
-
|
|
2867
|
+
let cookies = isServer ? forwardedCookies.get(cached) : undefined;
|
|
2868
|
+
if (isServer && !cookies) forwardedCookies.set(cached, cookies = new WeakSet());
|
|
2869
|
+
return "then" in v ? v.then(handleResponse(false, cookies), handleResponse(true, cookies)) : handleResponse(false, cookies)(v);
|
|
2807
2870
|
}
|
|
2808
|
-
function handleResponse(error) {
|
|
2871
|
+
function handleResponse(error, cookies) {
|
|
2809
2872
|
return async v => {
|
|
2810
2873
|
let enveloped;
|
|
2811
2874
|
let hasEnveloped = false;
|
|
@@ -2818,8 +2881,18 @@ function query(fn, name) {
|
|
|
2818
2881
|
if (v instanceof Response) {
|
|
2819
2882
|
const e = getRequestEvent();
|
|
2820
2883
|
if (e) {
|
|
2884
|
+
const headers = e.response.headers;
|
|
2885
|
+
const skipCookies = cookies?.has(headers);
|
|
2821
2886
|
for (const [key, value] of v.headers) {
|
|
2822
|
-
|
|
2887
|
+
// Keep metadata for the query's body off the document response.
|
|
2888
|
+
if (responseBodyHeaders.has(key)) continue;
|
|
2889
|
+
if (key === "set-cookie") {
|
|
2890
|
+
if (!skipCookies) headers.append(key, value);
|
|
2891
|
+
} else if (headers.get(key) !== value) headers.set(key, value);
|
|
2892
|
+
}
|
|
2893
|
+
// Each cached result contributes its cookies once per document.
|
|
2894
|
+
if (!skipCookies && v.headers.has("set-cookie")) {
|
|
2895
|
+
cookies?.add(headers);
|
|
2823
2896
|
}
|
|
2824
2897
|
}
|
|
2825
2898
|
let url = v.headers.get(LocationHeader);
|
|
@@ -2902,6 +2975,7 @@ query.set = (key, value) => {
|
|
|
2902
2975
|
const now = Date.now();
|
|
2903
2976
|
let cached = cache.get(key);
|
|
2904
2977
|
if (cached) {
|
|
2978
|
+
isServer && forwardedCookies.delete(cached);
|
|
2905
2979
|
cached[0] = now;
|
|
2906
2980
|
cached[1] = Promise.resolve(value);
|
|
2907
2981
|
cached[2] = value;
|
|
@@ -2949,7 +3023,7 @@ function hashKey(args) {
|
|
|
2949
3023
|
return JSON.stringify(args, (_, val) => isPlainObject(val) ? Object.keys(val).sort().reduce((result, key) => {
|
|
2950
3024
|
result[key] = val[key];
|
|
2951
3025
|
return result;
|
|
2952
|
-
},
|
|
3026
|
+
}, Object.create(null)) : val);
|
|
2953
3027
|
}
|
|
2954
3028
|
function isPlainObject(obj) {
|
|
2955
3029
|
let proto;
|
package/dist/routers/factory.jsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*@refresh skip*/
|
|
2
|
-
import { createSignal, getOwner, onCleanup, onSettled, runWithOwner,
|
|
2
|
+
import { createSignal, getOwner, onCleanup, onSettled, runWithOwner, untrack } from "solid-js";
|
|
3
3
|
// standalone imports: `DEV` is undefined in solid's production build and
|
|
4
4
|
// `OBSERVE` outside its observe/dev builds, so app bundlers fold the
|
|
5
5
|
// `DEV &&` diagnostics and the `OBSERVE &&` attribution out of shipped bundles
|
|
@@ -53,7 +53,6 @@ function describeNavigation(match, next, written, from) {
|
|
|
53
53
|
const t = to();
|
|
54
54
|
return t === undefined ? undefined : new URL(t, mockBase).pathname;
|
|
55
55
|
};
|
|
56
|
-
const matches = (pathname) => untrack(() => match(pathname));
|
|
57
56
|
const ref = {
|
|
58
57
|
kind: "navigation",
|
|
59
58
|
get to() {
|
|
@@ -61,22 +60,61 @@ function describeNavigation(match, next, written, from) {
|
|
|
61
60
|
},
|
|
62
61
|
from,
|
|
63
62
|
get name() {
|
|
64
|
-
|
|
65
|
-
if (p === undefined)
|
|
66
|
-
return undefined;
|
|
67
|
-
const m = matches(p);
|
|
68
|
-
return m.length ? m[m.length - 1].route.pattern || "/" : p;
|
|
63
|
+
return routeName(match, pathname());
|
|
69
64
|
},
|
|
70
65
|
get params() {
|
|
71
|
-
|
|
72
|
-
const m = p === undefined ? [] : matches(p);
|
|
73
|
-
return m.length ? mergeParams(m) : undefined;
|
|
66
|
+
return routeParams(match, pathname());
|
|
74
67
|
}
|
|
75
68
|
};
|
|
76
69
|
if (next._navigation !== undefined && next._navigation > 1)
|
|
77
70
|
ref.redirect = next._navigation - 1;
|
|
78
71
|
return ref;
|
|
79
72
|
}
|
|
73
|
+
/** The parametrized route `pathname` matches — the last match's pattern, `/` for the root; the pathname itself when nothing matched. */
|
|
74
|
+
function routeName(match, pathname) {
|
|
75
|
+
if (pathname === undefined)
|
|
76
|
+
return undefined;
|
|
77
|
+
const m = untrack(() => match(pathname));
|
|
78
|
+
return m.length ? m[m.length - 1].route.pattern || "/" : pathname;
|
|
79
|
+
}
|
|
80
|
+
/** The params the matched chain bound, merged; `undefined` when nothing matched. */
|
|
81
|
+
function routeParams(match, pathname) {
|
|
82
|
+
if (pathname === undefined)
|
|
83
|
+
return undefined;
|
|
84
|
+
const m = untrack(() => match(pathname));
|
|
85
|
+
return m.length ? mergeParams(m) : undefined;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The route the document arrived on, for solid's observe tier: the same
|
|
89
|
+
* declaration as a navigation's, around the work that establishes the
|
|
90
|
+
* router's initial match — building its context — since a fresh document has
|
|
91
|
+
* no location write to wrap. On the client the engine opens it at the time
|
|
92
|
+
* origin and settles it as the context is built (the first `"navigation"`
|
|
93
|
+
* record, `initial: true`); on the server the same call names the request's
|
|
94
|
+
* `"render"` record (`RenderEvent.route`). Every field is a getter read at
|
|
95
|
+
* settle, as for a navigation: `to` is the location as the router then holds
|
|
96
|
+
* it — the arrival normalized onto the base path when it came in empty, a
|
|
97
|
+
* write the frame itself took (see the setter) — and `name`/`params` match
|
|
98
|
+
* it then, so a lazy subtree that resolved while the server rendered names
|
|
99
|
+
* the exact route.
|
|
100
|
+
*/
|
|
101
|
+
function describeInitial(match, location) {
|
|
102
|
+
const to = () => untrack(location).value;
|
|
103
|
+
const pathname = () => new URL(to(), mockBase).pathname;
|
|
104
|
+
return {
|
|
105
|
+
kind: "navigation",
|
|
106
|
+
initial: true,
|
|
107
|
+
get to() {
|
|
108
|
+
return to();
|
|
109
|
+
},
|
|
110
|
+
get name() {
|
|
111
|
+
return routeName(match, pathname());
|
|
112
|
+
},
|
|
113
|
+
get params() {
|
|
114
|
+
return routeParams(match, pathname());
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
80
118
|
/** Wraps a history adapter in the integration signal the router core consumes. Must run under a reactive owner. */
|
|
81
119
|
function createIntegration(history, match) {
|
|
82
120
|
let committing = false;
|
|
@@ -91,8 +129,6 @@ function createIntegration(history, match) {
|
|
|
91
129
|
const signal = [
|
|
92
130
|
read,
|
|
93
131
|
(next) => {
|
|
94
|
-
if (sharedConfig.registry && !sharedConfig.done)
|
|
95
|
-
sharedConfig.done = true;
|
|
96
132
|
// What the write resolved to, or undefined when there was nothing to
|
|
97
133
|
// write (see `resolveLocationWrite`).
|
|
98
134
|
let written;
|
|
@@ -128,8 +164,15 @@ function createIntegration(history, match) {
|
|
|
128
164
|
// while a navigation is pending, which is the `from` a hop wants too.
|
|
129
165
|
// A write the no-op rule drops is still declared: the engine settles a
|
|
130
166
|
// navigation whose write "did not survive the equality gate" on the spot.
|
|
131
|
-
|
|
132
|
-
|
|
167
|
+
// A write from NO location is not a navigation: it is the arrival being
|
|
168
|
+
// normalized (`createRouterContext` moving an empty location — a hash
|
|
169
|
+
// history with no hash, a memory history seeded with "" — onto the base
|
|
170
|
+
// path), and the arrival is already declared by the initial frame open
|
|
171
|
+
// around the context build (`describeInitial`), which this write then
|
|
172
|
+
// stamps. The location is never empty again after that first write.
|
|
173
|
+
const from = untrack(read).value;
|
|
174
|
+
OBSERVE && from
|
|
175
|
+
? OBSERVE.attribution.withOrigin(describeNavigation(match, next, () => written, from), commit)
|
|
133
176
|
: commit();
|
|
134
177
|
}
|
|
135
178
|
];
|
|
@@ -210,11 +253,16 @@ export function createRouter(config) {
|
|
|
210
253
|
? staticIntegration(props.url, config.history && config.history.utils)
|
|
211
254
|
: createIntegration(history || browserHistory(), matchPath);
|
|
212
255
|
let context;
|
|
213
|
-
const
|
|
256
|
+
const buildContext = () => createRouterContext(integration, branches, () => context, {
|
|
214
257
|
base: basePath,
|
|
215
258
|
singleFlight: config.singleFlight,
|
|
216
259
|
transformUrl: config.transformUrl
|
|
217
260
|
});
|
|
261
|
+
// The route the document arrived on, declared around the initial match
|
|
262
|
+
// (see `describeInitial`); folded out of shipped bundles with `OBSERVE`.
|
|
263
|
+
const routerState = OBSERVE
|
|
264
|
+
? OBSERVE.attribution.withOrigin(describeInitial(matchPath, integration.signal[0]), buildContext)
|
|
265
|
+
: buildContext();
|
|
218
266
|
if (!isServer) {
|
|
219
267
|
setupNativeEvents({
|
|
220
268
|
preload: config.preloadLinks,
|
package/dist/routing.js
CHANGED
|
@@ -502,7 +502,8 @@ function createLocation(path, state, queryWrapper) {
|
|
|
502
502
|
const pathname = createMemo(() => url().pathname);
|
|
503
503
|
const search = createMemo(() => url().search);
|
|
504
504
|
const hash = createMemo(() => url().hash);
|
|
505
|
-
|
|
505
|
+
// Pathname and hash changes do not affect the query.
|
|
506
|
+
const queryFn = createMemo(() => extractSearchParams(new URL(search(), mockBase)));
|
|
506
507
|
return {
|
|
507
508
|
get pathname() {
|
|
508
509
|
return pathname();
|
package/dist/utils.js
CHANGED
|
@@ -43,7 +43,7 @@ export function validateSearch(schema, raw) {
|
|
|
43
43
|
return outcome;
|
|
44
44
|
}
|
|
45
45
|
export function extractSearchParams(url) {
|
|
46
|
-
const params =
|
|
46
|
+
const params = Object.create(null);
|
|
47
47
|
url.searchParams.forEach((value, key) => {
|
|
48
48
|
if (key in params) {
|
|
49
49
|
if (Array.isArray(params[key]))
|
|
@@ -54,7 +54,8 @@ export function extractSearchParams(url) {
|
|
|
54
54
|
else
|
|
55
55
|
params[key] = value;
|
|
56
56
|
});
|
|
57
|
-
|
|
57
|
+
// Keep __proto__ as an own property on the plain object returned to callers.
|
|
58
|
+
return { ...params };
|
|
58
59
|
}
|
|
59
60
|
export function createMatcher(path, partial, matchFilters) {
|
|
60
61
|
const [pattern, splat] = path.split("/*", 2);
|
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.32",
|
|
10
10
|
"homepage": "https://github.com/solidjs/solid-router#readme",
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
@@ -38,22 +38,22 @@
|
|
|
38
38
|
"@rollup/plugin-node-resolve": "15.3.0",
|
|
39
39
|
"@rollup/plugin-terser": "0.4.4",
|
|
40
40
|
"@solidjs/vite-plugin": "3.0.0-next.35",
|
|
41
|
-
"@solidjs/web": "^2.0.0-rc.
|
|
41
|
+
"@solidjs/web": "^2.0.0-rc.13",
|
|
42
42
|
"@types/jest": "^29.5.14",
|
|
43
43
|
"@types/node": "^22.10.0",
|
|
44
44
|
"babel-preset-solid": "^2.0.0-rc.2",
|
|
45
45
|
"jsdom": "^25.0.1",
|
|
46
46
|
"prettier": "^3.4.1",
|
|
47
47
|
"rollup": "^4.27.4",
|
|
48
|
-
"solid-js": "^2.0.0-rc.
|
|
48
|
+
"solid-js": "^2.0.0-rc.13",
|
|
49
49
|
"typescript": "^5.7.2",
|
|
50
50
|
"vite": "^8.2.2",
|
|
51
51
|
"vitest": "^4.1.11"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
|
-
"@solidjs/web": "^2.0.0-rc.
|
|
54
|
+
"@solidjs/web": "^2.0.0-rc.13",
|
|
55
55
|
"filesystem-routing": ">=0.4.0",
|
|
56
|
-
"solid-js": "^2.0.0-rc.
|
|
56
|
+
"solid-js": "^2.0.0-rc.13"
|
|
57
57
|
},
|
|
58
58
|
"peerDependenciesMeta": {
|
|
59
59
|
"filesystem-routing": {
|