@solidjs/router 2.0.0-next.20 → 2.0.0-next.21
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/action.js +41 -15
- package/dist/index.js +40 -15
- package/package.json +5 -5
package/dist/data/action.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { $TRACK, action as createSolidAction, createMemo, onCleanup, getOwner } from "solid-js";
|
|
2
2
|
import { isResponseEnvelope, isServer, REVALIDATE_HEADER } from "@solidjs/web";
|
|
3
|
-
import { createServerReference, decodeResponsePayload, parseServerFunctionUrl, subscribeFlightData } from "@solidjs/web/server-functions";
|
|
3
|
+
import { createServerReference, decodeRedirectHeaderValue, decodeResponsePayload, parseServerFunctionUrl, REDIRECT_HEADER, subscribeFlightData } from "@solidjs/web/server-functions";
|
|
4
4
|
// The explicit /server specifier is safe here: the only call site is
|
|
5
5
|
// server-guarded, so client builds tree-shake the codec away.
|
|
6
6
|
import { decodeFlashCookie } from "@solidjs/web/server-functions/server";
|
|
@@ -68,11 +68,13 @@ export function handleFormAction(evt, router, actionBase) {
|
|
|
68
68
|
* carries everything an invocation needs — the function id in the path
|
|
69
69
|
* (`<endpoint>/<id>`) and any bound `.with()` arguments (plain JSON in
|
|
70
70
|
* `?args`, which the server prepends for natural-encoding bodies exactly as
|
|
71
|
-
* it does for no-JS posts) — so the FormData is posted
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
71
|
+
* it does for no-JS posts) — so the FormData is posted through the
|
|
72
|
+
* server-function transport, which addresses its own scripted calls at the
|
|
73
|
+
* url's data sibling (`<endpoint>/data/<id>`, solidjs/solid#3094) with the
|
|
74
|
+
* bound query intact: submissions, `aria-busy`, redirects, revalidation,
|
|
75
|
+
* and single-flight all flow through the normal action machinery.
|
|
76
|
+
* Registered under the rendered url, so repeat submits reuse it (and a
|
|
77
|
+
* later real registration overrides it).
|
|
76
78
|
*/
|
|
77
79
|
function createServerFormAction(url) {
|
|
78
80
|
const id = parseServerFunctionUrl(url);
|
|
@@ -290,6 +292,10 @@ async function settleActionResult(result) {
|
|
|
290
292
|
// response's metadata (and so the default revalidation pass must not run
|
|
291
293
|
// again and wipe the freshly seeded cache).
|
|
292
294
|
let flightApplications = 0;
|
|
295
|
+
// The statuses fetch follows (Fetch §2.2.3) — the set the server masks into
|
|
296
|
+
// the redirect carrier for scripted calls, and the set a locally-produced
|
|
297
|
+
// redirect() envelope wears for real.
|
|
298
|
+
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
293
299
|
/**
|
|
294
300
|
* Registers the router as the single-flight consumer of the server function
|
|
295
301
|
* transport. Subscribing is the opt-in: while registered, the transport
|
|
@@ -309,23 +315,43 @@ export function setupFlightDataConsumer(router) {
|
|
|
309
315
|
}
|
|
310
316
|
/**
|
|
311
317
|
* Applies a server function response's integration metadata: `X-Revalidate`
|
|
312
|
-
* keys invalidate,
|
|
313
|
-
* data seeds the query cache, and matching entries
|
|
314
|
-
* the flight-data consumer and the action response
|
|
315
|
-
* metadata-bearing responses when no flight data was
|
|
318
|
+
* keys invalidate, the redirect carrier navigates (soft when same-origin,
|
|
319
|
+
* hard otherwise), flight data seeds the query cache, and matching entries
|
|
320
|
+
* revalidate. Shared by the flight-data consumer and the action response
|
|
321
|
+
* path (which still sees metadata-bearing responses when no flight data was
|
|
322
|
+
* collected).
|
|
316
323
|
*/
|
|
317
324
|
function applyResponseMetadata(metadata, navigate, flightData) {
|
|
318
325
|
let keys;
|
|
319
326
|
if (metadata) {
|
|
320
327
|
if (metadata.headers.has(REVALIDATE_HEADER))
|
|
321
328
|
keys = metadata.headers.get(REVALIDATE_HEADER).split(",");
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
329
|
+
// The carrier delivers the target RESOLVED to an absolute url
|
|
330
|
+
// (solidjs/solid#3102), so the soft/hard split is a real origin
|
|
331
|
+
// comparison — never a guess from how the author spelled the target,
|
|
332
|
+
// which sent `redirect("/")` and `redirect(new URL("/", url).href)`
|
|
333
|
+
// down different navigation paths (solidjs/solid#3107). A redirect
|
|
334
|
+
// produced locally (a client-side action's `redirect()`) never crossed
|
|
335
|
+
// the wire, so no carrier was attached: it is the real 3xx with its
|
|
336
|
+
// Location, resolved against the page it runs in. A `Location` on any
|
|
337
|
+
// other status is the author's data (a 201's created-at) and never
|
|
338
|
+
// navigates. Same-origin targets navigate softly under the router;
|
|
339
|
+
// anything else leaves the app, so the document goes with it.
|
|
340
|
+
// `replace` matches what HTTP gives a form post: the target takes the
|
|
341
|
+
// submission's place in history rather than stacking on it.
|
|
342
|
+
const carried = decodeRedirectHeaderValue(metadata.headers.get(REDIRECT_HEADER));
|
|
343
|
+
const local = !carried && REDIRECT_STATUSES.has(metadata.status) && metadata.headers.get("Location");
|
|
344
|
+
const target = carried
|
|
345
|
+
? new URL(carried.url)
|
|
346
|
+
: local
|
|
347
|
+
? new URL(local, window.location.href)
|
|
348
|
+
: undefined;
|
|
349
|
+
if (target) {
|
|
350
|
+
if (target.origin === window.location.origin) {
|
|
351
|
+
navigate(target.pathname + target.search + target.hash, { replace: true });
|
|
326
352
|
}
|
|
327
353
|
else {
|
|
328
|
-
|
|
354
|
+
window.location.href = target.href;
|
|
329
355
|
}
|
|
330
356
|
}
|
|
331
357
|
}
|
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, 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, decodeResponsePayload, parseServerFunctionUrl, createServerReference } from '@solidjs/web/server-functions';
|
|
3
|
+
import { subscribeFlightData, decodeRedirectHeaderValue, REDIRECT_HEADER, decodeResponsePayload, parseServerFunctionUrl, 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;
|
|
@@ -2562,11 +2562,13 @@ function handleFormAction(evt, router, actionBase) {
|
|
|
2562
2562
|
* carries everything an invocation needs — the function id in the path
|
|
2563
2563
|
* (`<endpoint>/<id>`) and any bound `.with()` arguments (plain JSON in
|
|
2564
2564
|
* `?args`, which the server prepends for natural-encoding bodies exactly as
|
|
2565
|
-
* it does for no-JS posts) — so the FormData is posted
|
|
2566
|
-
*
|
|
2567
|
-
*
|
|
2568
|
-
*
|
|
2569
|
-
*
|
|
2565
|
+
* it does for no-JS posts) — so the FormData is posted through the
|
|
2566
|
+
* server-function transport, which addresses its own scripted calls at the
|
|
2567
|
+
* url's data sibling (`<endpoint>/data/<id>`, solidjs/solid#3094) with the
|
|
2568
|
+
* bound query intact: submissions, `aria-busy`, redirects, revalidation,
|
|
2569
|
+
* and single-flight all flow through the normal action machinery.
|
|
2570
|
+
* Registered under the rendered url, so repeat submits reuse it (and a
|
|
2571
|
+
* later real registration overrides it).
|
|
2570
2572
|
*/
|
|
2571
2573
|
function createServerFormAction(url) {
|
|
2572
2574
|
const id = parseServerFunctionUrl(url);
|
|
@@ -2791,6 +2793,11 @@ async function settleActionResult(result) {
|
|
|
2791
2793
|
// again and wipe the freshly seeded cache).
|
|
2792
2794
|
let flightApplications = 0;
|
|
2793
2795
|
|
|
2796
|
+
// The statuses fetch follows (Fetch §2.2.3) — the set the server masks into
|
|
2797
|
+
// the redirect carrier for scripted calls, and the set a locally-produced
|
|
2798
|
+
// redirect() envelope wears for real.
|
|
2799
|
+
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
2800
|
+
|
|
2794
2801
|
/**
|
|
2795
2802
|
* Registers the router as the single-flight consumer of the server function
|
|
2796
2803
|
* transport. Subscribing is the opt-in: while registered, the transport
|
|
@@ -2813,21 +2820,39 @@ function setupFlightDataConsumer(router) {
|
|
|
2813
2820
|
|
|
2814
2821
|
/**
|
|
2815
2822
|
* Applies a server function response's integration metadata: `X-Revalidate`
|
|
2816
|
-
* keys invalidate,
|
|
2817
|
-
* data seeds the query cache, and matching entries
|
|
2818
|
-
* the flight-data consumer and the action response
|
|
2819
|
-
* metadata-bearing responses when no flight data was
|
|
2823
|
+
* keys invalidate, the redirect carrier navigates (soft when same-origin,
|
|
2824
|
+
* hard otherwise), flight data seeds the query cache, and matching entries
|
|
2825
|
+
* revalidate. Shared by the flight-data consumer and the action response
|
|
2826
|
+
* path (which still sees metadata-bearing responses when no flight data was
|
|
2827
|
+
* collected).
|
|
2820
2828
|
*/
|
|
2821
2829
|
function applyResponseMetadata(metadata, navigate, flightData) {
|
|
2822
2830
|
let keys;
|
|
2823
2831
|
if (metadata) {
|
|
2824
2832
|
if (metadata.headers.has(REVALIDATE_HEADER)) keys = metadata.headers.get(REVALIDATE_HEADER).split(",");
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2833
|
+
// The carrier delivers the target RESOLVED to an absolute url
|
|
2834
|
+
// (solidjs/solid#3102), so the soft/hard split is a real origin
|
|
2835
|
+
// comparison — never a guess from how the author spelled the target,
|
|
2836
|
+
// which sent `redirect("/")` and `redirect(new URL("/", url).href)`
|
|
2837
|
+
// down different navigation paths (solidjs/solid#3107). A redirect
|
|
2838
|
+
// produced locally (a client-side action's `redirect()`) never crossed
|
|
2839
|
+
// the wire, so no carrier was attached: it is the real 3xx with its
|
|
2840
|
+
// Location, resolved against the page it runs in. A `Location` on any
|
|
2841
|
+
// other status is the author's data (a 201's created-at) and never
|
|
2842
|
+
// navigates. Same-origin targets navigate softly under the router;
|
|
2843
|
+
// anything else leaves the app, so the document goes with it.
|
|
2844
|
+
// `replace` matches what HTTP gives a form post: the target takes the
|
|
2845
|
+
// submission's place in history rather than stacking on it.
|
|
2846
|
+
const carried = decodeRedirectHeaderValue(metadata.headers.get(REDIRECT_HEADER));
|
|
2847
|
+
const local = !carried && REDIRECT_STATUSES.has(metadata.status) && metadata.headers.get("Location");
|
|
2848
|
+
const target = carried ? new URL(carried.url) : local ? new URL(local, window.location.href) : undefined;
|
|
2849
|
+
if (target) {
|
|
2850
|
+
if (target.origin === window.location.origin) {
|
|
2851
|
+
navigate(target.pathname + target.search + target.hash, {
|
|
2852
|
+
replace: true
|
|
2853
|
+
});
|
|
2829
2854
|
} else {
|
|
2830
|
-
|
|
2855
|
+
window.location.href = target.href;
|
|
2831
2856
|
}
|
|
2832
2857
|
}
|
|
2833
2858
|
}
|
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.21",
|
|
10
10
|
"homepage": "https://github.com/solidjs/solid-router#readme",
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
@@ -38,21 +38,21 @@
|
|
|
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.5",
|
|
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.5",
|
|
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.
|
|
55
|
-
"solid-js": "^2.0.0-rc.
|
|
54
|
+
"@solidjs/web": "^2.0.0-rc.5",
|
|
55
|
+
"solid-js": "^2.0.0-rc.5"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"build": "rm -rf dist && tsc && rollup -c",
|