@solidjs/router 2.0.0-next.25 → 2.0.0-next.26

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 CHANGED
@@ -519,6 +519,8 @@ getUser.keyFor(5); // "users[5]"
519
519
 
520
520
  Revalidate with the `revalidate` export or by setting `revalidate` keys on action responses — the whole key invalidates every entry for the query, `keyFor` invalidates one.
521
521
 
522
+ A query may also redirect — a guard read that throws or returns `redirect()` (from `@solidjs/web`) navigates instead of resolving: same-origin targets navigate softly with `replace`, other origins leave the document, any `revalidate` keys on the response invalidate first, and the read itself stays pending so nothing renders the redirect as data. This holds for `"use server"` queries too, where the transport carries the redirect to the client rather than letting `fetch` follow it.
523
+
522
524
  ### `liveQuery` (experimental)
523
525
 
524
526
  `query`'s live sibling: a keyed query over a value-shaped stream. The function is an async iterable (typically an async generator server function) whose yields are successive **values of one logical query** — each yield is the current state, not an event — with the contract that it re-yields current state on every invocation:
@@ -1,15 +1,20 @@
1
1
  import { createSignal, getObserver, getOwner, onCleanup, sharedConfig, 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
- // 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
4
+ // the late-bound RPC seam (getServerFunctionRPC). The transport itself —
5
+ // the fetch RPC client + the seroval codec behind it — is deliberately NOT
6
+ // imported here: query() is in every router app's eager graph, and a static
7
+ // import of `decodeResponse` made every zero-server-function app ship
8
8
  // ~9 KB gz of codec it could never invoke. The transport registers itself
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
12
  import { getRequestEvent, getServerFunctionMetadata, getServerFunctionRPC, isResponseEnvelope, isServer, isServerFunction, REVALIDATE_HEADER } from "@solidjs/web";
13
+ // The redirect carrier's name and decoder are the exception: two pure,
14
+ // dependency-free bindings off a `sideEffects: false` entry, so they
15
+ // tree-shake to a few hundred bytes without dragging the codec in (the same
16
+ // bindings action.ts already imports statically).
17
+ import { decodeRedirectHeaderValue, REDIRECT_HEADER } from "@solidjs/web/server-functions";
13
18
  import { useRouter, getIntent, getInPreloadFn } from "../routing.js";
14
19
  const LocationHeader = "Location";
15
20
  const PRELOAD_TIMEOUT = 5000;
@@ -62,7 +67,12 @@ export function revalidate(key, force = true) {
62
67
  const now = Date.now();
63
68
  cacheKeyOp(key, entry => {
64
69
  force && (entry[0] = 0); //force cache miss
65
- entry[4][1](now); // retrigger live signals
70
+ // retrigger live signals. The version is the entry's fetch stamp, and
71
+ // a signal write of an equal value is a no-op — an entry fetched within
72
+ // this same millisecond (a mount whose redirect lands before the clock
73
+ // ticks) would otherwise never be told to refetch, and the surviving
74
+ // consumer paints stale. A sweep must notify unconditionally.
75
+ entry[4][1](v => (v === now ? now + 1 : now));
66
76
  });
67
77
  const keys = key === undefined ? undefined : Array.isArray(key) ? key : [key];
68
78
  for (const hook of revalidateHooks)
@@ -251,7 +261,27 @@ export function query(fn, name) {
251
261
  e.response.headers.set(key, value);
252
262
  }
253
263
  }
254
- const url = v.headers.get(LocationHeader);
264
+ let url = v.headers.get(LocationHeader);
265
+ // A `"use server"` redirect reaches a client-side read masked: the
266
+ // transport answers scripted callers with a 200, drops `Location`
267
+ // and carries "<status> <absolute-url>" in REDIRECT_HEADER instead.
268
+ // Decode it with the runtime's own reader, as action() does. The
269
+ // carrier arrives RESOLVED to an absolute url, so same-origin vs
270
+ // cross-origin is a real origin comparison: same-origin folds to
271
+ // a path the soft branch below navigates under the router, any
272
+ // other origin keeps its href and the document goes with it. This
273
+ // stays synchronous on purpose — navigate runs in the same tick as
274
+ // the `Location` branch would, so the transition semantics match.
275
+ if (url === null && !isServer && v.headers.has(REDIRECT_HEADER)) {
276
+ const carried = decodeRedirectHeaderValue(v.headers.get(REDIRECT_HEADER));
277
+ if (carried) {
278
+ const target = new URL(carried.url);
279
+ url =
280
+ target.origin === window.location.origin
281
+ ? target.pathname + target.search + target.hash
282
+ : target.href;
283
+ }
284
+ }
255
285
  if (url !== null) {
256
286
  // invalidate the redirect's revalidation keys before navigating so
257
287
  // the destination's preloads see the miss and fetch fresh (#580 thread).
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { getOwner, runWithOwner, createMemo, createRenderEffect, onCleanup, untrack, createContext, createSignal, useContext, NotReadyError, isPending, DEV, latest, createComponent, createRoot, Show, createEffect, sharedConfig, OBSERVE, onSettled, getObserver, $TRACK, action as action$1 } from 'solid-js';
2
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, decodeRedirectHeaderValue, REDIRECT_HEADER, decodeResponsePayload, parseServerFunctionActionUrl, createServerReference } from '@solidjs/web/server-functions';
3
+ import { REDIRECT_HEADER, decodeRedirectHeaderValue, subscribeFlightData, decodeResponsePayload, parseServerFunctionActionUrl, 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;
@@ -2457,7 +2457,12 @@ function revalidate(key, force = true) {
2457
2457
  const now = Date.now();
2458
2458
  cacheKeyOp(key, entry => {
2459
2459
  force && (entry[0] = 0); //force cache miss
2460
- entry[4][1](now); // retrigger live signals
2460
+ // retrigger live signals. The version is the entry's fetch stamp, and
2461
+ // a signal write of an equal value is a no-op — an entry fetched within
2462
+ // this same millisecond (a mount whose redirect lands before the clock
2463
+ // ticks) would otherwise never be told to refetch, and the surviving
2464
+ // consumer paints stale. A sweep must notify unconditionally.
2465
+ entry[4][1](v => v === now ? now + 1 : now);
2461
2466
  });
2462
2467
  const keys = key === undefined ? undefined : Array.isArray(key) ? key : [key];
2463
2468
  for (const hook of revalidateHooks) hook(keys, force);
@@ -2619,7 +2624,25 @@ function query(fn, name) {
2619
2624
  if (key == "set-cookie") e.response.headers.append("set-cookie", value);else e.response.headers.set(key, value);
2620
2625
  }
2621
2626
  }
2622
- const url = v.headers.get(LocationHeader);
2627
+ let url = v.headers.get(LocationHeader);
2628
+
2629
+ // A `"use server"` redirect reaches a client-side read masked: the
2630
+ // transport answers scripted callers with a 200, drops `Location`
2631
+ // and carries "<status> <absolute-url>" in REDIRECT_HEADER instead.
2632
+ // Decode it with the runtime's own reader, as action() does. The
2633
+ // carrier arrives RESOLVED to an absolute url, so same-origin vs
2634
+ // cross-origin is a real origin comparison: same-origin folds to
2635
+ // a path the soft branch below navigates under the router, any
2636
+ // other origin keeps its href and the document goes with it. This
2637
+ // stays synchronous on purpose — navigate runs in the same tick as
2638
+ // the `Location` branch would, so the transition semantics match.
2639
+ if (url === null && !isServer && v.headers.has(REDIRECT_HEADER)) {
2640
+ const carried = decodeRedirectHeaderValue(v.headers.get(REDIRECT_HEADER));
2641
+ if (carried) {
2642
+ const target = new URL(carried.url);
2643
+ url = target.origin === window.location.origin ? target.pathname + target.search + target.hash : target.href;
2644
+ }
2645
+ }
2623
2646
  if (url !== null) {
2624
2647
  // invalidate the redirect's revalidation keys before navigating so
2625
2648
  // the destination's preloads see the miss and fetch fresh (#580 thread).
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "Ryan Turnquist"
7
7
  ],
8
8
  "license": "MIT",
9
- "version": "2.0.0-next.25",
9
+ "version": "2.0.0-next.26",
10
10
  "homepage": "https://github.com/solidjs/solid-router#readme",
11
11
  "repository": {
12
12
  "type": "git",