@@ -858,15 +858,32 @@ type NoFn<T> = T extends Function ? never : T;
858858/**
859859 * The pending source for BARE `ssrSource: "client"` (no declared commit #0):
860860 * a hole the server can never fill. Reads throw a `NotReadyError` carrying
861- * this promise ; the `$clientHole` tag classifies the suspension as FINAL —
861+ * this thenable ; the `$clientHole` tag classifies the suspension as FINAL —
862862 * boundaries hand the position to the client (the "$$f" client-continue
863- * route) instead of awaiting a settle that will never come. One shared,
864- * never-settling instance: retry subscriptions attached to it (e.g.
865- * `subscribePendingRetry`) are inert by design.
863+ * route) instead of awaiting a settle that will never come.
864+ *
865+ * One shared instance, and deliberately NOT a native Promise. A never-
866+ * settling Promise still records every `then`/`await` against it in its
867+ * reaction list, and that list is reachable from the module scope — so each
868+ * retry subscription a derived read attached (`subscribePendingRetry` from
869+ * a `<Show when={client().length}>`, a `dynamic()` source memo, …) pinned
870+ * that request's computation, props and data for the life of the process
871+ * (#3657). This thenable's `then` drops its callbacks: subscribing to it is
872+ * inert at the object, not by convention at each call site. `await` and the
873+ * `Promise` combinators go through `Promise.resolve(thenable)`, which mints
874+ * a fresh never-settling promise per call and holds no reference back here.
866875 */
867- const CLIENT_HOLE : Promise < never > = /* @__PURE__ */ Object . assign ( new Promise < never > ( ( ) => { } ) , {
868- $clientHole : true
869- } ) ;
876+ type ClientHole = PromiseLike < never > & { $clientHole : true } ;
877+ const CLIENT_HOLE : ClientHole = /* @__PURE__ */ ( ( ) => {
878+ const hole = {
879+ $clientHole : true as const ,
880+ then : ( ) => hole ,
881+ catch : ( ) => hole ,
882+ finally : ( ) => hole
883+ } ;
884+ return Object . freeze ( hole ) as unknown as ClientHole ;
885+ } ) ( ) ;
886+ const isClientHole = ( source : unknown ) : boolean => source === CLIENT_HOLE ;
870887
871888/**
872889 * A final (client-hole) suspension is only meaningful where a `<Loading>`
@@ -2080,7 +2097,7 @@ function serverEffect<T>(
20802097 // response forever. Rethrow so the surrounding render (a Loading
20812098 // discovery pass — the read throws loudly anywhere else) escalates
20822099 // the suspension to the boundary, which hands off to the client.
2083- if ( source === CLIENT_HOLE ) throw err ;
2100+ if ( isClientHole ( source ) ) throw err ;
20842101 const retry = ( ) => {
20852102 if ( comp . disposed ) return ;
20862103 try {
@@ -2096,7 +2113,7 @@ function serverEffect<T>(
20962113 // is no render on the stack to escalate to, so swallow: the
20972114 // effect simply never fires server-side (the client runs it
20982115 // after hydration), instead of blocking the stream forever.
2099- if ( next !== CLIENT_HOLE ) ctx . block ( next . then ( retry , ( ) => { } ) ) ;
2116+ if ( ! isClientHole ( next ) ) ctx . block ( next . then ( retry , ( ) => { } ) ) ;
21002117 return ;
21012118 }
21022119 // Out-of-band by now — route to the boundary's error handler.
@@ -2279,7 +2296,7 @@ export function createOptimisticStore<T extends object = {}>(
22792296 */
22802297function createPendingProxy < T extends object > (
22812298 state : T ,
2282- source : Promise < any >
2299+ source : PromiseLike < any >
22832300) : [ proxy : Store < T > , markReady : ( frozenState ?: T ) => void , markError : ( error : any ) => void ] {
22842301 let status : 0 | 1 | 2 = 0 ;
22852302 let error : any ;
0 commit comments