@solidjs/router 2.0.0-next.32 → 2.0.0-next.34

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.
@@ -52,13 +52,40 @@ export function handleFormAction(evt, router, actionBase) {
52
52
  // stays a no-JS fallback.
53
53
  // Client-only actions (`https://action/`) are their module's JS by
54
54
  // definition, so a miss there falls through to native submission.
55
- const handler = actions.get(actionRef) || (serverAction && createServerFormAction(actionRef));
55
+ const handler = findAction(actionRef) || (serverAction && createServerFormAction(actionRef));
56
56
  if (handler) {
57
57
  evt.preventDefault();
58
58
  const data = new FormData(form, evt.submitter);
59
59
  handler.call({ r: router, f: form }, form.enctype === "multipart/form-data" ? data : new URLSearchParams(data));
60
60
  }
61
61
  }
62
+ /**
63
+ * Looks a rendered action url up in the registry. A server-rendered
64
+ * `.with()` url is only registered if this client made the same binding
65
+ * itself; otherwise its base action (the url without `?args`) is rebound to
66
+ * the rendered arguments, so the submission runs through that action's
67
+ * submit and settled hooks and is recorded under its base.
68
+ */
69
+ function findAction(url) {
70
+ const handler = actions.get(url);
71
+ if (handler)
72
+ return handler;
73
+ const query = url.indexOf("?");
74
+ if (query < 0)
75
+ return undefined;
76
+ const base = actions.get(url.slice(0, query));
77
+ const args = new URLSearchParams(url.slice(query)).get("args");
78
+ if (!base || args === null)
79
+ return undefined;
80
+ let bound;
81
+ try {
82
+ bound = JSON.parse(args);
83
+ }
84
+ catch {
85
+ return undefined;
86
+ }
87
+ return Array.isArray(bound) ? base.with(...bound) : undefined;
88
+ }
62
89
  /**
63
90
  * Synthesizes a router action for a server-rendered action url. The url
64
91
  * carries everything an invocation needs — the function id in the path
@@ -90,7 +117,7 @@ function createServerFormAction(url) {
90
117
  * the enctype conversion and the generic invocation happen here.
91
118
  */
92
119
  export function submitServerForm(router, url, form, data) {
93
- const handler = actions.get(url) || createServerFormAction(url);
120
+ const handler = findAction(url) || createServerFormAction(url);
94
121
  // not an address (`<endpoint>/<id>`) — not the server function convention;
95
122
  // nothing can run it, resubmit natively (submit() bypasses the delegated
96
123
  // handler)
@@ -244,9 +271,12 @@ function toAction(invoke, url, boundArgs = [], base = url, submitHooks = new Map
244
271
  return url;
245
272
  };
246
273
  fn.with = function (...args) {
274
+ const bound = [...boundArgs, ...args];
247
275
  const uri = new URL(url, mockBase);
248
- uri.searchParams.set("args", hashKey(args));
249
- return toAction(invoke, (uri.origin === "https://action" ? uri.origin : "") + uri.pathname + uri.search, [...boundArgs, ...args], base, submitHooks, settledHooks);
276
+ // the server prepends `args` to the submitted arguments, so it must carry
277
+ // the whole binding, not just this call's part of a chain
278
+ uri.searchParams.set("args", hashKey(bound));
279
+ return toAction(invoke, (uri.origin === "https://action" ? uri.origin : "") + uri.pathname + uri.search, bound, base, submitHooks, settledHooks);
250
280
  };
251
281
  fn.onSubmit = function (hook) {
252
282
  const id = Symbol("actionOnSubmitHook");
@@ -1,4 +1,4 @@
1
- import { createSignal, getObserver, getOwner, isHydratable, onCleanup, untrack } from "solid-js";
1
+ import { createSignal, getObserver, getOwner, isHydratable, isHydrating, 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 —
@@ -37,6 +37,13 @@ const forwardedCookies = /* #__PURE__ */ new WeakMap();
37
37
  // hold values the server computed while rendering THIS page, so their age is
38
38
  // anchored here — not at whenever a late query() call happens to consume one.
39
39
  const bootTime = Date.now();
40
+ // Captured at module load: a hydrating client runs a serialized node's
41
+ // compute once as a dependency trace with the global `Promise` swapped for a
42
+ // never-settling mock, and a query() read inside that trace still consumes
43
+ // and caches its flight entry. Wrapped with the global there, a settled
44
+ // entry would cache a promise that never resolves, hanging every navigation
45
+ // that reuses it.
46
+ const NativePromise = Promise;
40
47
  // Every hydratable read ships its entry: the writer keeps the first write
41
48
  // per key, so a read skipped in a NoHydration zone never hides the entry
42
49
  // from an island that reads the same key later.
@@ -165,11 +172,18 @@ export function query(fn, name) {
165
172
  tracking = true;
166
173
  onCleanup(() => cached[4].count--);
167
174
  }
175
+ // A read with no navigation in flight while hydrating is claiming server
176
+ // DOM, so it takes the cached entry at any age — the rule adoption below
177
+ // applies to flight entries. It can come from a boundary resuming long
178
+ // after boot, once the entry was adopted and its first reader disposed;
179
+ // refetching there would run inside Solid's hydration trace, where fetch
180
+ // and Promise never settle, and cache that.
168
181
  if (cached &&
169
182
  cached[0] &&
170
183
  (isServer ||
171
184
  intent === "native" ||
172
185
  cached[4].count ||
186
+ (!intent && isHydrating()) ||
173
187
  Date.now() - cached[0] < PRELOAD_TIMEOUT)) {
174
188
  if (tracking) {
175
189
  cached[4].count++;
@@ -210,8 +224,8 @@ export function query(fn, name) {
210
224
  entry.status === "pending"
211
225
  ? entry.promise
212
226
  : entry.status === "resolved"
213
- ? Promise.resolve(entry.value)
214
- : Promise.reject(entry.error);
227
+ ? NativePromise.resolve(entry.value)
228
+ : NativePromise.reject(entry.error);
215
229
  }
216
230
  // A payload too old for this intent stays in the registry: it is
217
231
  // inert while the refetched entry below lives, but a later claim
@@ -220,11 +234,10 @@ export function query(fn, name) {
220
234
  if (!adopted)
221
235
  res = fn(...args);
222
236
  // Adopted entries are stamped at boot — when their data was actually
223
- // fetched — not at consumption. The hydration render burst still dedups
224
- // through the normal window/count reuse above (it happens within
225
- // milliseconds of boot), while a navigation that comes along later sees
226
- // the payload's true age and refetches past PRELOAD_TIMEOUT, exactly as
227
- // it would for a preload performed when the server fetched the data.
237
+ // fetched — not at consumption. Later hydration reads still reuse them
238
+ // through the claim rule above, while a navigation that comes along later
239
+ // sees the payload's true age and refetches past PRELOAD_TIMEOUT, exactly
240
+ // as it would for a preload performed when the server fetched the data.
228
241
  const stamp = adopted ? bootTime : now;
229
242
  if (cached) {
230
243
  isServer && forwardedCookies.delete(cached);
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
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';
1
+ import { getOwner, runWithOwner, createMemo, createRenderEffect, onCleanup, untrack, createContext, createSignal, useContext, NotReadyError, DEV, isPending, latest, createComponent, createRoot, Show, createEffect, OBSERVE, onSettled, getObserver, isHydrating, isHydratable, $TRACK, action as action$1 } from 'solid-js';
2
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
 
@@ -1190,6 +1190,17 @@ let flashDecoder;
1190
1190
  function provideFlashDecoder(decoder) {
1191
1191
  flashDecoder || (flashDecoder = decoder);
1192
1192
  }
1193
+
1194
+ /**
1195
+ * A request's flash cookie and its decode, shared by every router created
1196
+ * for that request. A server render can re-create the router — a suspension
1197
+ * under `<Errored>` retries from the nearest hole above it, which may sit
1198
+ * above the router — so state kept on one router instance would restart the
1199
+ * decode with every re-creation and the seeding read would never settle.
1200
+ * Keyed by the request event, so it lives exactly as long as the request.
1201
+ */
1202
+
1203
+ const flashStates = new WeakMap();
1193
1204
  let preloadIntent;
1194
1205
  function getIntent() {
1195
1206
  return preloadIntent || useOptionalContext(RouterContextObj)?.intent?.();
@@ -1228,63 +1239,68 @@ function createRouterContext(integration, branches, getContext, options = {}) {
1228
1239
  // unread outcome must not haunt a later request's render. Only detection
1229
1240
  // and clearing happen here (the runtime's isomorphic half); the raw header
1230
1241
  // is stashed and decoding waits for the action-provided codec, read when
1231
- // the lazily allocated submissions signal below first initializes.
1232
- let flashCookieHeader;
1242
+ // the lazily allocated submissions signal below first initializes. Both
1243
+ // happen once per request: a router re-created for the same request finds
1244
+ // the request's flash state (see `flashStates`) and neither clears again
1245
+ // nor decodes again.
1246
+ let flash;
1233
1247
  if (isServer) {
1234
1248
  const e = getRequestEvent();
1235
1249
  if (e && !(e.router && e.router.submission)) {
1236
- const cookieHeader = e.request.headers.get("cookie");
1237
- if (hasFlashCookie(cookieHeader)) {
1238
- flashCookieHeader = cookieHeader;
1239
- // one-shot: clear it even when unreadable so it can't haunt later renders
1240
- if (e.response && e.response.headers) e.response.headers.append("Set-Cookie", clearFlashCookie());
1250
+ flash = flashStates.get(e);
1251
+ if (!flash) {
1252
+ const cookieHeader = e.request.headers.get("cookie");
1253
+ if (hasFlashCookie(cookieHeader)) {
1254
+ flashStates.set(e, flash = {
1255
+ cookieHeader
1256
+ });
1257
+ // one-shot: clear it even when unreadable so it can't haunt later renders
1258
+ if (e.response && e.response.headers) e.response.headers.append("Set-Cookie", clearFlashCookie());
1259
+ }
1241
1260
  }
1242
1261
  }
1243
1262
  }
1244
1263
 
1245
- // The decode, at most once per request: the decoder may answer with a
1246
- // Promise (the cookie is encrypted; the runtime's decodeFlashCookie is
1247
- // async), and this cache is what keeps the parked read's rerun from
1248
- // restarting it — resumption finds the settled outcome and just reads it.
1249
- // A decoder that rejects reads as "no flash", matching the runtime's own
1250
- // malformed-cookie semantics.
1251
- let flashDecode;
1252
-
1253
1264
  // The seeding read, as a memo: NotReadyError must surface from a reactive
1254
1265
  // node the graph can park and retry — never from router setup, which no
1255
- // boundary guards — and the memo bounds the recompute to this function;
1256
- // a parked reader resumes into the settled cache above, never a second
1257
- // decode. Created only when a flash cookie actually arrived (server-only
1258
- // by construction: flashCookieHeader is only ever set there), and
1266
+ // boundary guards — and the memo bounds the recompute to this function.
1267
+ // The decode runs at most once per request: the decoder may answer with a
1268
+ // Promise (the cookie is encrypted; the runtime's decodeFlashCookie is
1269
+ // async), and the request's cached state is what keeps a parked read's
1270
+ // rerun — or a re-created router's first read — from restarting it. A
1271
+ // decoder that rejects reads as "no flash", matching the runtime's own
1272
+ // malformed-cookie semantics. Created only when a flash cookie actually
1273
+ // arrived (server-only by construction: `flash` is only ever set there), and
1259
1274
  // - `lazy`: server memos compute eagerly by default — deferred to first
1260
1275
  // read, a request whose submissions are never read never decodes;
1261
1276
  // - `transparent`: the memo exists on the server only, so its owner
1262
1277
  // must not consume a hydration-id slot — the client, which seeds
1263
1278
  // submissions as [] without ever creating this memo, would miss it
1264
1279
  // and every sibling id would shift.
1265
- const flashSubmission = flashCookieHeader !== undefined ? createMemo(() => {
1280
+ const flashSubmission = flash !== undefined ? createMemo(() => {
1281
+ const state = flash;
1266
1282
  if (!flashDecoder) return undefined;
1267
- if (!flashDecode) {
1268
- const promise = flashDecoder(flashCookieHeader).then(value => {
1269
- flashDecode = {
1283
+ if (!state.decode) {
1284
+ const promise = flashDecoder(state.cookieHeader).then(value => {
1285
+ state.decode = {
1270
1286
  done: true,
1271
1287
  value
1272
1288
  };
1273
1289
  }, () => {
1274
- flashDecode = {
1290
+ state.decode = {
1275
1291
  done: true,
1276
1292
  value: undefined
1277
1293
  };
1278
1294
  });
1279
- flashDecode = {
1295
+ state.decode = {
1280
1296
  done: false,
1281
1297
  promise
1282
1298
  };
1283
1299
  }
1284
1300
  // SSR carries the Promise through NotReadyError so the parked
1285
1301
  // reader can resume, exactly like the lazy matches above.
1286
- if (!flashDecode.done) throw new NotReadyError(flashDecode.promise);
1287
- return flashDecode.value;
1302
+ if (!state.decode.done) throw new NotReadyError(state.decode.promise);
1303
+ return state.decode.value;
1288
1304
  }, {
1289
1305
  lazy: true,
1290
1306
  transparent: true
@@ -2663,6 +2679,13 @@ const forwardedCookies = /* #__PURE__ */new WeakMap();
2663
2679
  // hold values the server computed while rendering THIS page, so their age is
2664
2680
  // anchored here — not at whenever a late query() call happens to consume one.
2665
2681
  const bootTime = Date.now();
2682
+ // Captured at module load: a hydrating client runs a serialized node's
2683
+ // compute once as a dependency trace with the global `Promise` swapped for a
2684
+ // never-settling mock, and a query() read inside that trace still consumes
2685
+ // and caches its flight entry. Wrapped with the global there, a settled
2686
+ // entry would cache a promise that never resolves, hanging every navigation
2687
+ // that reuses it.
2688
+ const NativePromise = Promise;
2666
2689
 
2667
2690
  // Every hydratable read ships its entry: the writer keeps the first write
2668
2691
  // per key, so a read skipped in a NoHydration zone never hides the entry
@@ -2787,7 +2810,14 @@ function query(fn, name) {
2787
2810
  tracking = true;
2788
2811
  onCleanup(() => cached[4].count--);
2789
2812
  }
2790
- if (cached && cached[0] && (isServer || intent === "native" || cached[4].count || Date.now() - cached[0] < PRELOAD_TIMEOUT$1)) {
2813
+
2814
+ // A read with no navigation in flight while hydrating is claiming server
2815
+ // DOM, so it takes the cached entry at any age — the rule adoption below
2816
+ // applies to flight entries. It can come from a boundary resuming long
2817
+ // after boot, once the entry was adopted and its first reader disposed;
2818
+ // refetching there would run inside Solid's hydration trace, where fetch
2819
+ // and Promise never settle, and cache that.
2820
+ if (cached && cached[0] && (isServer || intent === "native" || cached[4].count || !intent && isHydrating() || Date.now() - cached[0] < PRELOAD_TIMEOUT$1)) {
2791
2821
  if (tracking) {
2792
2822
  cached[4].count++;
2793
2823
  cached[4][0](); // track
@@ -2822,7 +2852,7 @@ function query(fn, name) {
2822
2852
  const entry = takeHydrationValue(key); // hydrating
2823
2853
  if (entry) {
2824
2854
  adopted = true;
2825
- res = entry.status === "pending" ? entry.promise : entry.status === "resolved" ? Promise.resolve(entry.value) : Promise.reject(entry.error);
2855
+ res = entry.status === "pending" ? entry.promise : entry.status === "resolved" ? NativePromise.resolve(entry.value) : NativePromise.reject(entry.error);
2826
2856
  }
2827
2857
  // A payload too old for this intent stays in the registry: it is
2828
2858
  // inert while the refetched entry below lives, but a later claim
@@ -2831,11 +2861,10 @@ function query(fn, name) {
2831
2861
  if (!adopted) res = fn(...args);
2832
2862
 
2833
2863
  // Adopted entries are stamped at boot — when their data was actually
2834
- // fetched — not at consumption. The hydration render burst still dedups
2835
- // through the normal window/count reuse above (it happens within
2836
- // milliseconds of boot), while a navigation that comes along later sees
2837
- // the payload's true age and refetches past PRELOAD_TIMEOUT, exactly as
2838
- // it would for a preload performed when the server fetched the data.
2864
+ // fetched — not at consumption. Later hydration reads still reuse them
2865
+ // through the claim rule above, while a navigation that comes along later
2866
+ // sees the payload's true age and refetches past PRELOAD_TIMEOUT, exactly
2867
+ // as it would for a preload performed when the server fetched the data.
2839
2868
  const stamp = adopted ? bootTime : now;
2840
2869
  if (cached) {
2841
2870
  isServer && forwardedCookies.delete(cached);
@@ -3073,7 +3102,7 @@ function handleFormAction(evt, router, actionBase) {
3073
3102
  // stays a no-JS fallback.
3074
3103
  // Client-only actions (`https://action/`) are their module's JS by
3075
3104
  // definition, so a miss there falls through to native submission.
3076
- const handler = actions.get(actionRef) || serverAction && createServerFormAction(actionRef);
3105
+ const handler = findAction(actionRef) || serverAction && createServerFormAction(actionRef);
3077
3106
  if (handler) {
3078
3107
  evt.preventDefault();
3079
3108
  const data = new FormData(form, evt.submitter);
@@ -3084,6 +3113,30 @@ function handleFormAction(evt, router, actionBase) {
3084
3113
  }
3085
3114
  }
3086
3115
 
3116
+ /**
3117
+ * Looks a rendered action url up in the registry. A server-rendered
3118
+ * `.with()` url is only registered if this client made the same binding
3119
+ * itself; otherwise its base action (the url without `?args`) is rebound to
3120
+ * the rendered arguments, so the submission runs through that action's
3121
+ * submit and settled hooks and is recorded under its base.
3122
+ */
3123
+ function findAction(url) {
3124
+ const handler = actions.get(url);
3125
+ if (handler) return handler;
3126
+ const query = url.indexOf("?");
3127
+ if (query < 0) return undefined;
3128
+ const base = actions.get(url.slice(0, query));
3129
+ const args = new URLSearchParams(url.slice(query)).get("args");
3130
+ if (!base || args === null) return undefined;
3131
+ let bound;
3132
+ try {
3133
+ bound = JSON.parse(args);
3134
+ } catch {
3135
+ return undefined;
3136
+ }
3137
+ return Array.isArray(bound) ? base.with(...bound) : undefined;
3138
+ }
3139
+
3087
3140
  /**
3088
3141
  * Synthesizes a router action for a server-rendered action url. The url
3089
3142
  * carries everything an invocation needs — the function id in the path
@@ -3117,7 +3170,7 @@ function createServerFormAction(url) {
3117
3170
  * the enctype conversion and the generic invocation happen here.
3118
3171
  */
3119
3172
  function submitServerForm(router, url, form, data) {
3120
- const handler = actions.get(url) || createServerFormAction(url);
3173
+ const handler = findAction(url) || createServerFormAction(url);
3121
3174
  // not an address (`<endpoint>/<id>`) — not the server function convention;
3122
3175
  // nothing can run it, resubmit natively (submit() bypasses the delegated
3123
3176
  // handler)
@@ -3269,9 +3322,12 @@ function toAction(invoke, url, boundArgs = [], base = url, submitHooks = new Map
3269
3322
  return url;
3270
3323
  };
3271
3324
  fn.with = function (...args) {
3325
+ const bound = [...boundArgs, ...args];
3272
3326
  const uri = new URL(url, mockBase);
3273
- uri.searchParams.set("args", hashKey(args));
3274
- return toAction(invoke, (uri.origin === "https://action" ? uri.origin : "") + uri.pathname + uri.search, [...boundArgs, ...args], base, submitHooks, settledHooks);
3327
+ // the server prepends `args` to the submitted arguments, so it must carry
3328
+ // the whole binding, not just this call's part of a chain
3329
+ uri.searchParams.set("args", hashKey(bound));
3330
+ return toAction(invoke, (uri.origin === "https://action" ? uri.origin : "") + uri.pathname + uri.search, bound, base, submitHooks, settledHooks);
3275
3331
  };
3276
3332
  fn.onSubmit = function (hook) {
3277
3333
  const id = Symbol("actionOnSubmitHook");
package/dist/routing.js CHANGED
@@ -556,6 +556,7 @@ let flashDecoder;
556
556
  export function provideFlashDecoder(decoder) {
557
557
  flashDecoder || (flashDecoder = decoder);
558
558
  }
559
+ const flashStates = new WeakMap();
559
560
  let preloadIntent;
560
561
  export function getIntent() {
561
562
  return preloadIntent || useOptionalContext(RouterContextObj)?.intent?.();
@@ -592,56 +593,60 @@ export function createRouterContext(integration, branches, getContext, options =
592
593
  // unread outcome must not haunt a later request's render. Only detection
593
594
  // and clearing happen here (the runtime's isomorphic half); the raw header
594
595
  // is stashed and decoding waits for the action-provided codec, read when
595
- // the lazily allocated submissions signal below first initializes.
596
- let flashCookieHeader;
596
+ // the lazily allocated submissions signal below first initializes. Both
597
+ // happen once per request: a router re-created for the same request finds
598
+ // the request's flash state (see `flashStates`) and neither clears again
599
+ // nor decodes again.
600
+ let flash;
597
601
  if (isServer) {
598
602
  const e = getRequestEvent();
599
603
  if (e && !(e.router && e.router.submission)) {
600
- const cookieHeader = e.request.headers.get("cookie");
601
- if (hasFlashCookie(cookieHeader)) {
602
- flashCookieHeader = cookieHeader;
603
- // one-shot: clear it even when unreadable so it can't haunt later renders
604
- if (e.response && e.response.headers)
605
- e.response.headers.append("Set-Cookie", clearFlashCookie());
604
+ flash = flashStates.get(e);
605
+ if (!flash) {
606
+ const cookieHeader = e.request.headers.get("cookie");
607
+ if (hasFlashCookie(cookieHeader)) {
608
+ flashStates.set(e, (flash = { cookieHeader }));
609
+ // one-shot: clear it even when unreadable so it can't haunt later renders
610
+ if (e.response && e.response.headers)
611
+ e.response.headers.append("Set-Cookie", clearFlashCookie());
612
+ }
606
613
  }
607
614
  }
608
615
  }
609
- // The decode, at most once per request: the decoder may answer with a
610
- // Promise (the cookie is encrypted; the runtime's decodeFlashCookie is
611
- // async), and this cache is what keeps the parked read's rerun from
612
- // restarting it — resumption finds the settled outcome and just reads it.
613
- // A decoder that rejects reads as "no flash", matching the runtime's own
614
- // malformed-cookie semantics.
615
- let flashDecode;
616
616
  // The seeding read, as a memo: NotReadyError must surface from a reactive
617
617
  // node the graph can park and retry — never from router setup, which no
618
- // boundary guards — and the memo bounds the recompute to this function;
619
- // a parked reader resumes into the settled cache above, never a second
620
- // decode. Created only when a flash cookie actually arrived (server-only
621
- // by construction: flashCookieHeader is only ever set there), and
618
+ // boundary guards — and the memo bounds the recompute to this function.
619
+ // The decode runs at most once per request: the decoder may answer with a
620
+ // Promise (the cookie is encrypted; the runtime's decodeFlashCookie is
621
+ // async), and the request's cached state is what keeps a parked read's
622
+ // rerun — or a re-created router's first read — from restarting it. A
623
+ // decoder that rejects reads as "no flash", matching the runtime's own
624
+ // malformed-cookie semantics. Created only when a flash cookie actually
625
+ // arrived (server-only by construction: `flash` is only ever set there), and
622
626
  // - `lazy`: server memos compute eagerly by default — deferred to first
623
627
  // read, a request whose submissions are never read never decodes;
624
628
  // - `transparent`: the memo exists on the server only, so its owner
625
629
  // must not consume a hydration-id slot — the client, which seeds
626
630
  // submissions as [] without ever creating this memo, would miss it
627
631
  // and every sibling id would shift.
628
- const flashSubmission = flashCookieHeader !== undefined
632
+ const flashSubmission = flash !== undefined
629
633
  ? createMemo(() => {
634
+ const state = flash;
630
635
  if (!flashDecoder)
631
636
  return undefined;
632
- if (!flashDecode) {
633
- const promise = flashDecoder(flashCookieHeader).then(value => {
634
- flashDecode = { done: true, value };
637
+ if (!state.decode) {
638
+ const promise = flashDecoder(state.cookieHeader).then(value => {
639
+ state.decode = { done: true, value };
635
640
  }, () => {
636
- flashDecode = { done: true, value: undefined };
641
+ state.decode = { done: true, value: undefined };
637
642
  });
638
- flashDecode = { done: false, promise };
643
+ state.decode = { done: false, promise };
639
644
  }
640
645
  // SSR carries the Promise through NotReadyError so the parked
641
646
  // reader can resume, exactly like the lazy matches above.
642
- if (!flashDecode.done)
643
- throw new NotReadyError(flashDecode.promise);
644
- return flashDecode.value;
647
+ if (!state.decode.done)
648
+ throw new NotReadyError(state.decode.promise);
649
+ return state.decode.value;
645
650
  }, { lazy: true, transparent: true })
646
651
  : undefined;
647
652
  let submissions;
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "Ryan Turnquist"
7
7
  ],
8
8
  "license": "MIT",
9
- "version": "2.0.0-next.32",
9
+ "version": "2.0.0-next.34",
10
10
  "homepage": "https://github.com/solidjs/solid-router#readme",
11
11
  "repository": {
12
12
  "type": "git",