@solidjs/router 2.0.0-next.20 → 2.0.0-next.22
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 +42 -15
- package/dist/index.js +102 -19
- package/dist/routing.d.ts +17 -1
- package/dist/routing.js +46 -17
- 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);
|
|
@@ -116,6 +118,7 @@ function installRouterIntegrations() {
|
|
|
116
118
|
if (isServer) {
|
|
117
119
|
// Server-only: initSubmissions only decodes during SSR, so client builds
|
|
118
120
|
// tree-shake the codec (which now lives behind the runtime's server entry).
|
|
121
|
+
// The codec is async from @solidjs/web 2.0.0-rc.7 (encrypted cookie).
|
|
119
122
|
provideFlashDecoder(decodeFlashCookie);
|
|
120
123
|
}
|
|
121
124
|
else {
|
|
@@ -290,6 +293,10 @@ async function settleActionResult(result) {
|
|
|
290
293
|
// response's metadata (and so the default revalidation pass must not run
|
|
291
294
|
// again and wipe the freshly seeded cache).
|
|
292
295
|
let flightApplications = 0;
|
|
296
|
+
// The statuses fetch follows (Fetch §2.2.3) — the set the server masks into
|
|
297
|
+
// the redirect carrier for scripted calls, and the set a locally-produced
|
|
298
|
+
// redirect() envelope wears for real.
|
|
299
|
+
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
293
300
|
/**
|
|
294
301
|
* Registers the router as the single-flight consumer of the server function
|
|
295
302
|
* transport. Subscribing is the opt-in: while registered, the transport
|
|
@@ -309,23 +316,43 @@ export function setupFlightDataConsumer(router) {
|
|
|
309
316
|
}
|
|
310
317
|
/**
|
|
311
318
|
* 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
|
|
319
|
+
* keys invalidate, the redirect carrier navigates (soft when same-origin,
|
|
320
|
+
* hard otherwise), flight data seeds the query cache, and matching entries
|
|
321
|
+
* revalidate. Shared by the flight-data consumer and the action response
|
|
322
|
+
* path (which still sees metadata-bearing responses when no flight data was
|
|
323
|
+
* collected).
|
|
316
324
|
*/
|
|
317
325
|
function applyResponseMetadata(metadata, navigate, flightData) {
|
|
318
326
|
let keys;
|
|
319
327
|
if (metadata) {
|
|
320
328
|
if (metadata.headers.has(REVALIDATE_HEADER))
|
|
321
329
|
keys = metadata.headers.get(REVALIDATE_HEADER).split(",");
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
330
|
+
// The carrier delivers the target RESOLVED to an absolute url
|
|
331
|
+
// (solidjs/solid#3102), so the soft/hard split is a real origin
|
|
332
|
+
// comparison — never a guess from how the author spelled the target,
|
|
333
|
+
// which sent `redirect("/")` and `redirect(new URL("/", url).href)`
|
|
334
|
+
// down different navigation paths (solidjs/solid#3107). A redirect
|
|
335
|
+
// produced locally (a client-side action's `redirect()`) never crossed
|
|
336
|
+
// the wire, so no carrier was attached: it is the real 3xx with its
|
|
337
|
+
// Location, resolved against the page it runs in. A `Location` on any
|
|
338
|
+
// other status is the author's data (a 201's created-at) and never
|
|
339
|
+
// navigates. Same-origin targets navigate softly under the router;
|
|
340
|
+
// anything else leaves the app, so the document goes with it.
|
|
341
|
+
// `replace` matches what HTTP gives a form post: the target takes the
|
|
342
|
+
// submission's place in history rather than stacking on it.
|
|
343
|
+
const carried = decodeRedirectHeaderValue(metadata.headers.get(REDIRECT_HEADER));
|
|
344
|
+
const local = !carried && REDIRECT_STATUSES.has(metadata.status) && metadata.headers.get("Location");
|
|
345
|
+
const target = carried
|
|
346
|
+
? new URL(carried.url)
|
|
347
|
+
: local
|
|
348
|
+
? new URL(local, window.location.href)
|
|
349
|
+
: undefined;
|
|
350
|
+
if (target) {
|
|
351
|
+
if (target.origin === window.location.origin) {
|
|
352
|
+
navigate(target.pathname + target.search + target.hash, { replace: true });
|
|
326
353
|
}
|
|
327
354
|
else {
|
|
328
|
-
|
|
355
|
+
window.location.href = target.href;
|
|
329
356
|
}
|
|
330
357
|
}
|
|
331
358
|
}
|
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;
|
|
@@ -1097,7 +1097,12 @@ function provideFlightConsumer(factory) {
|
|
|
1097
1097
|
* decoder is always installed before useSubmission can read — and a
|
|
1098
1098
|
* router-only app, where it never installs, has no actions that could have
|
|
1099
1099
|
* produced a flash cookie in the first place.
|
|
1100
|
+
*
|
|
1101
|
+
* The decoder is async: the flash cookie is encrypted (solidjs/solid#3239),
|
|
1102
|
+
* so the runtime's `decodeFlashCookie` decrypts through WebCrypto and the
|
|
1103
|
+
* seeding read parks on the not-ready protocol until the decode settles.
|
|
1100
1104
|
*/
|
|
1105
|
+
|
|
1101
1106
|
let flashDecoder;
|
|
1102
1107
|
function provideFlashDecoder(decoder) {
|
|
1103
1108
|
flashDecoder || (flashDecoder = decoder);
|
|
@@ -1153,6 +1158,54 @@ function createRouterContext(integration, branches, getContext, options = {}) {
|
|
|
1153
1158
|
}
|
|
1154
1159
|
}
|
|
1155
1160
|
}
|
|
1161
|
+
|
|
1162
|
+
// The decode, at most once per request: the decoder may answer with a
|
|
1163
|
+
// Promise (the cookie is encrypted; the runtime's decodeFlashCookie is
|
|
1164
|
+
// async), and this cache is what keeps the parked read's rerun from
|
|
1165
|
+
// restarting it — resumption finds the settled outcome and just reads it.
|
|
1166
|
+
// A decoder that rejects reads as "no flash", matching the runtime's own
|
|
1167
|
+
// malformed-cookie semantics.
|
|
1168
|
+
let flashDecode;
|
|
1169
|
+
|
|
1170
|
+
// The seeding read, as a memo: NotReadyError must surface from a reactive
|
|
1171
|
+
// node the graph can park and retry — never from router setup, which no
|
|
1172
|
+
// boundary guards — and the memo bounds the recompute to this function;
|
|
1173
|
+
// a parked reader resumes into the settled cache above, never a second
|
|
1174
|
+
// decode. Created only when a flash cookie actually arrived (server-only
|
|
1175
|
+
// by construction: flashCookieHeader is only ever set there), and
|
|
1176
|
+
// - `lazy`: server memos compute eagerly by default — deferred to first
|
|
1177
|
+
// read, a request whose submissions are never read never decodes;
|
|
1178
|
+
// - `transparent`: the memo exists on the server only, so its owner
|
|
1179
|
+
// must not consume a hydration-id slot — the client, which seeds
|
|
1180
|
+
// submissions as [] without ever creating this memo, would miss it
|
|
1181
|
+
// and every sibling id would shift.
|
|
1182
|
+
const flashSubmission = flashCookieHeader !== undefined ? createMemo(() => {
|
|
1183
|
+
if (!flashDecoder) return undefined;
|
|
1184
|
+
if (!flashDecode) {
|
|
1185
|
+
const promise = flashDecoder(flashCookieHeader).then(value => {
|
|
1186
|
+
flashDecode = {
|
|
1187
|
+
done: true,
|
|
1188
|
+
value
|
|
1189
|
+
};
|
|
1190
|
+
}, () => {
|
|
1191
|
+
flashDecode = {
|
|
1192
|
+
done: true,
|
|
1193
|
+
value: undefined
|
|
1194
|
+
};
|
|
1195
|
+
});
|
|
1196
|
+
flashDecode = {
|
|
1197
|
+
done: false,
|
|
1198
|
+
promise
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
// SSR carries the Promise through NotReadyError so the parked
|
|
1202
|
+
// reader can resume, exactly like the lazy matches above.
|
|
1203
|
+
if (!flashDecode.done) throw new NotReadyError(flashDecode.promise);
|
|
1204
|
+
return flashDecode.value;
|
|
1205
|
+
}, {
|
|
1206
|
+
lazy: true,
|
|
1207
|
+
transparent: true
|
|
1208
|
+
}) : undefined;
|
|
1156
1209
|
let submissions;
|
|
1157
1210
|
|
|
1158
1211
|
// NotReadyError's source must be a reactive async node, not the raw
|
|
@@ -1384,12 +1437,16 @@ function createRouterContext(integration, branches, getContext, options = {}) {
|
|
|
1384
1437
|
// Seeds the initial submission from a no-JS form post: the server
|
|
1385
1438
|
// function runtime redirected back with the outcome in a one-shot flash
|
|
1386
1439
|
// cookie (its default no-JS convention), consumed eagerly above and
|
|
1387
|
-
// decoded
|
|
1388
|
-
// exactly as a scripted submission would.
|
|
1389
|
-
//
|
|
1440
|
+
// decoded through the flashSubmission memo — so the post-redirect SSR
|
|
1441
|
+
// renders useSubmission() state exactly as a scripted submission would.
|
|
1442
|
+
// The memo read may throw NotReadyError while the (encrypted) cookie
|
|
1443
|
+
// decodes; the assignment in the submissions getter never completed, so
|
|
1444
|
+
// the resumed rerun retries it against the settled decode. An explicitly
|
|
1445
|
+
// pre-seeded `event.router.submission` (framework integrations) takes
|
|
1446
|
+
// precedence.
|
|
1390
1447
|
function initSubmissions() {
|
|
1391
1448
|
const e = getRequestEvent();
|
|
1392
|
-
const submission = e && e.router && e.router.submission ||
|
|
1449
|
+
const submission = e && e.router && e.router.submission || flashSubmission && flashSubmission();
|
|
1393
1450
|
if (!submission) return [];
|
|
1394
1451
|
return [{
|
|
1395
1452
|
...submission,
|
|
@@ -2562,11 +2619,13 @@ function handleFormAction(evt, router, actionBase) {
|
|
|
2562
2619
|
* carries everything an invocation needs — the function id in the path
|
|
2563
2620
|
* (`<endpoint>/<id>`) and any bound `.with()` arguments (plain JSON in
|
|
2564
2621
|
* `?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
|
-
*
|
|
2622
|
+
* it does for no-JS posts) — so the FormData is posted through the
|
|
2623
|
+
* server-function transport, which addresses its own scripted calls at the
|
|
2624
|
+
* url's data sibling (`<endpoint>/data/<id>`, solidjs/solid#3094) with the
|
|
2625
|
+
* bound query intact: submissions, `aria-busy`, redirects, revalidation,
|
|
2626
|
+
* and single-flight all flow through the normal action machinery.
|
|
2627
|
+
* Registered under the rendered url, so repeat submits reuse it (and a
|
|
2628
|
+
* later real registration overrides it).
|
|
2570
2629
|
*/
|
|
2571
2630
|
function createServerFormAction(url) {
|
|
2572
2631
|
const id = parseServerFunctionUrl(url);
|
|
@@ -2614,6 +2673,7 @@ function installRouterIntegrations() {
|
|
|
2614
2673
|
if (isServer) {
|
|
2615
2674
|
// Server-only: initSubmissions only decodes during SSR, so client builds
|
|
2616
2675
|
// tree-shake the codec (which now lives behind the runtime's server entry).
|
|
2676
|
+
// The codec is async from @solidjs/web 2.0.0-rc.7 (encrypted cookie).
|
|
2617
2677
|
provideFlashDecoder(decodeFlashCookie);
|
|
2618
2678
|
} else {
|
|
2619
2679
|
setRouterFormHandler(handleFormAction);
|
|
@@ -2791,6 +2851,11 @@ async function settleActionResult(result) {
|
|
|
2791
2851
|
// again and wipe the freshly seeded cache).
|
|
2792
2852
|
let flightApplications = 0;
|
|
2793
2853
|
|
|
2854
|
+
// The statuses fetch follows (Fetch §2.2.3) — the set the server masks into
|
|
2855
|
+
// the redirect carrier for scripted calls, and the set a locally-produced
|
|
2856
|
+
// redirect() envelope wears for real.
|
|
2857
|
+
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
2858
|
+
|
|
2794
2859
|
/**
|
|
2795
2860
|
* Registers the router as the single-flight consumer of the server function
|
|
2796
2861
|
* transport. Subscribing is the opt-in: while registered, the transport
|
|
@@ -2813,21 +2878,39 @@ function setupFlightDataConsumer(router) {
|
|
|
2813
2878
|
|
|
2814
2879
|
/**
|
|
2815
2880
|
* 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
|
|
2881
|
+
* keys invalidate, the redirect carrier navigates (soft when same-origin,
|
|
2882
|
+
* hard otherwise), flight data seeds the query cache, and matching entries
|
|
2883
|
+
* revalidate. Shared by the flight-data consumer and the action response
|
|
2884
|
+
* path (which still sees metadata-bearing responses when no flight data was
|
|
2885
|
+
* collected).
|
|
2820
2886
|
*/
|
|
2821
2887
|
function applyResponseMetadata(metadata, navigate, flightData) {
|
|
2822
2888
|
let keys;
|
|
2823
2889
|
if (metadata) {
|
|
2824
2890
|
if (metadata.headers.has(REVALIDATE_HEADER)) keys = metadata.headers.get(REVALIDATE_HEADER).split(",");
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2891
|
+
// The carrier delivers the target RESOLVED to an absolute url
|
|
2892
|
+
// (solidjs/solid#3102), so the soft/hard split is a real origin
|
|
2893
|
+
// comparison — never a guess from how the author spelled the target,
|
|
2894
|
+
// which sent `redirect("/")` and `redirect(new URL("/", url).href)`
|
|
2895
|
+
// down different navigation paths (solidjs/solid#3107). A redirect
|
|
2896
|
+
// produced locally (a client-side action's `redirect()`) never crossed
|
|
2897
|
+
// the wire, so no carrier was attached: it is the real 3xx with its
|
|
2898
|
+
// Location, resolved against the page it runs in. A `Location` on any
|
|
2899
|
+
// other status is the author's data (a 201's created-at) and never
|
|
2900
|
+
// navigates. Same-origin targets navigate softly under the router;
|
|
2901
|
+
// anything else leaves the app, so the document goes with it.
|
|
2902
|
+
// `replace` matches what HTTP gives a form post: the target takes the
|
|
2903
|
+
// submission's place in history rather than stacking on it.
|
|
2904
|
+
const carried = decodeRedirectHeaderValue(metadata.headers.get(REDIRECT_HEADER));
|
|
2905
|
+
const local = !carried && REDIRECT_STATUSES.has(metadata.status) && metadata.headers.get("Location");
|
|
2906
|
+
const target = carried ? new URL(carried.url) : local ? new URL(local, window.location.href) : undefined;
|
|
2907
|
+
if (target) {
|
|
2908
|
+
if (target.origin === window.location.origin) {
|
|
2909
|
+
navigate(target.pathname + target.search + target.hash, {
|
|
2910
|
+
replace: true
|
|
2911
|
+
});
|
|
2829
2912
|
} else {
|
|
2830
|
-
|
|
2913
|
+
window.location.href = target.href;
|
|
2831
2914
|
}
|
|
2832
2915
|
}
|
|
2833
2916
|
}
|
package/dist/routing.d.ts
CHANGED
|
@@ -216,7 +216,22 @@ export declare function getRouteMatches(branches: Branch[], location: string): R
|
|
|
216
216
|
export declare function mergeParams(matches: RouteMatch[]): Params;
|
|
217
217
|
export declare function registerFlightRouter(router: RouterContext): () => void;
|
|
218
218
|
export declare function provideFlightConsumer(factory: (router: RouterContext) => () => void): void;
|
|
219
|
-
|
|
219
|
+
/**
|
|
220
|
+
* The flash-cookie codec, provided by the action side (data/action.ts) so
|
|
221
|
+
* the router core never carries it: the core consumes the cookie eagerly
|
|
222
|
+
* per request (detection + one-shot clear via the runtime's isomorphic half)
|
|
223
|
+
* but defers decoding to this slot, read when the submissions signal
|
|
224
|
+
* initializes. Actions are created at module scope, so on the server the
|
|
225
|
+
* decoder is always installed before useSubmission can read — and a
|
|
226
|
+
* router-only app, where it never installs, has no actions that could have
|
|
227
|
+
* produced a flash cookie in the first place.
|
|
228
|
+
*
|
|
229
|
+
* The decoder is async: the flash cookie is encrypted (solidjs/solid#3239),
|
|
230
|
+
* so the runtime's `decodeFlashCookie` decrypts through WebCrypto and the
|
|
231
|
+
* seeding read parks on the not-ready protocol until the decode settles.
|
|
232
|
+
*/
|
|
233
|
+
type FlashDecoder = (cookieHeader: string | null) => Promise<FlashSubmission | undefined>;
|
|
234
|
+
export declare function provideFlashDecoder(decoder: FlashDecoder): void;
|
|
220
235
|
export declare function getIntent(): Intent | undefined;
|
|
221
236
|
export declare function getInPreloadFn(): boolean;
|
|
222
237
|
export declare function setInPreloadFn(value: boolean): void;
|
|
@@ -226,3 +241,4 @@ export declare function createRouterContext(integration: RouterIntegration, bran
|
|
|
226
241
|
transformUrl?: (url: string) => string;
|
|
227
242
|
}): RouterContext;
|
|
228
243
|
export declare function createRouteContext(router: RouterContext, parent: RouteContext, outlet: () => JSX.Element, match: () => RouteMatch, matches?: () => RouteMatch[]): RouteContext;
|
|
244
|
+
export {};
|
package/dist/routing.js
CHANGED
|
@@ -541,16 +541,6 @@ export function provideFlightConsumer(factory) {
|
|
|
541
541
|
flightRouters.set(router, factory(router));
|
|
542
542
|
}
|
|
543
543
|
}
|
|
544
|
-
/**
|
|
545
|
-
* The flash-cookie codec, provided by the action side (data/action.ts) so
|
|
546
|
-
* the router core never carries it: the core consumes the cookie eagerly
|
|
547
|
-
* per request (detection + one-shot clear via the runtime's isomorphic half)
|
|
548
|
-
* but defers decoding to this slot, read when the submissions signal
|
|
549
|
-
* initializes. Actions are created at module scope, so on the server the
|
|
550
|
-
* decoder is always installed before useSubmission can read — and a
|
|
551
|
-
* router-only app, where it never installs, has no actions that could have
|
|
552
|
-
* produced a flash cookie in the first place.
|
|
553
|
-
*/
|
|
554
544
|
let flashDecoder;
|
|
555
545
|
export function provideFlashDecoder(decoder) {
|
|
556
546
|
flashDecoder || (flashDecoder = decoder);
|
|
@@ -605,6 +595,44 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
605
595
|
}
|
|
606
596
|
}
|
|
607
597
|
}
|
|
598
|
+
// The decode, at most once per request: the decoder may answer with a
|
|
599
|
+
// Promise (the cookie is encrypted; the runtime's decodeFlashCookie is
|
|
600
|
+
// async), and this cache is what keeps the parked read's rerun from
|
|
601
|
+
// restarting it — resumption finds the settled outcome and just reads it.
|
|
602
|
+
// A decoder that rejects reads as "no flash", matching the runtime's own
|
|
603
|
+
// malformed-cookie semantics.
|
|
604
|
+
let flashDecode;
|
|
605
|
+
// The seeding read, as a memo: NotReadyError must surface from a reactive
|
|
606
|
+
// node the graph can park and retry — never from router setup, which no
|
|
607
|
+
// boundary guards — and the memo bounds the recompute to this function;
|
|
608
|
+
// a parked reader resumes into the settled cache above, never a second
|
|
609
|
+
// decode. Created only when a flash cookie actually arrived (server-only
|
|
610
|
+
// by construction: flashCookieHeader is only ever set there), and
|
|
611
|
+
// - `lazy`: server memos compute eagerly by default — deferred to first
|
|
612
|
+
// read, a request whose submissions are never read never decodes;
|
|
613
|
+
// - `transparent`: the memo exists on the server only, so its owner
|
|
614
|
+
// must not consume a hydration-id slot — the client, which seeds
|
|
615
|
+
// submissions as [] without ever creating this memo, would miss it
|
|
616
|
+
// and every sibling id would shift.
|
|
617
|
+
const flashSubmission = flashCookieHeader !== undefined
|
|
618
|
+
? createMemo(() => {
|
|
619
|
+
if (!flashDecoder)
|
|
620
|
+
return undefined;
|
|
621
|
+
if (!flashDecode) {
|
|
622
|
+
const promise = flashDecoder(flashCookieHeader).then(value => {
|
|
623
|
+
flashDecode = { done: true, value };
|
|
624
|
+
}, () => {
|
|
625
|
+
flashDecode = { done: true, value: undefined };
|
|
626
|
+
});
|
|
627
|
+
flashDecode = { done: false, promise };
|
|
628
|
+
}
|
|
629
|
+
// SSR carries the Promise through NotReadyError so the parked
|
|
630
|
+
// reader can resume, exactly like the lazy matches above.
|
|
631
|
+
if (!flashDecode.done)
|
|
632
|
+
throw new NotReadyError(flashDecode.promise);
|
|
633
|
+
return flashDecode.value;
|
|
634
|
+
}, { lazy: true, transparent: true })
|
|
635
|
+
: undefined;
|
|
608
636
|
let submissions;
|
|
609
637
|
// NotReadyError's source must be a reactive async node, not the raw
|
|
610
638
|
// Promise. Keep one reader per boundary for this router owner so rejection
|
|
@@ -843,15 +871,16 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
843
871
|
// Seeds the initial submission from a no-JS form post: the server
|
|
844
872
|
// function runtime redirected back with the outcome in a one-shot flash
|
|
845
873
|
// cookie (its default no-JS convention), consumed eagerly above and
|
|
846
|
-
// decoded
|
|
847
|
-
// exactly as a scripted submission would.
|
|
848
|
-
//
|
|
874
|
+
// decoded through the flashSubmission memo — so the post-redirect SSR
|
|
875
|
+
// renders useSubmission() state exactly as a scripted submission would.
|
|
876
|
+
// The memo read may throw NotReadyError while the (encrypted) cookie
|
|
877
|
+
// decodes; the assignment in the submissions getter never completed, so
|
|
878
|
+
// the resumed rerun retries it against the settled decode. An explicitly
|
|
879
|
+
// pre-seeded `event.router.submission` (framework integrations) takes
|
|
880
|
+
// precedence.
|
|
849
881
|
function initSubmissions() {
|
|
850
882
|
const e = getRequestEvent();
|
|
851
|
-
const submission = (e && e.router && e.router.submission) ||
|
|
852
|
-
(flashDecoder && flashCookieHeader !== undefined
|
|
853
|
-
? flashDecoder(flashCookieHeader)
|
|
854
|
-
: undefined);
|
|
883
|
+
const submission = (e && e.router && e.router.submission) || (flashSubmission && flashSubmission());
|
|
855
884
|
if (!submission)
|
|
856
885
|
return [];
|
|
857
886
|
return [
|
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.22",
|
|
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.7",
|
|
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.7",
|
|
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.7",
|
|
55
|
+
"solid-js": "^2.0.0-rc.7"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"build": "rm -rf dist && tsc && rollup -c",
|