@tanstack/solid-query 6.0.0-beta.6 → 6.0.0-beta.8

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.
@@ -13,8 +13,10 @@ import {
13
13
  runWithOwner,
14
14
  snapshot,
15
15
  untrack,
16
+ useContext,
16
17
  } from 'solid-js'
17
18
  import { useQueryClient } from './QueryClientProvider'
19
+ import { HydrationCoordinatorContext } from './hydrationChannel'
18
20
  import { useIsRestoring } from './isRestoring'
19
21
  import type { UseBaseQueryOptions } from './types'
20
22
  import type { Accessor } from 'solid-js'
@@ -97,8 +99,14 @@ function reconcileFn<TData, TError>(
97
99
  }
98
100
 
99
101
  /**
100
- * Solid's `onHydrated` functionality will silently "fail" (hydrate with an empty object)
101
- * if the resource data is not serializable.
102
+ * Prepare an observer result for SSR serialization: the resolved resource
103
+ * value is serialized by seroval, which cannot handle functions, so strip
104
+ * `refetch` (and the infinite-query pagers). They come back when the
105
+ * observer attaches on the client.
106
+ *
107
+ * The query's dehydrated cache state does not ride the observer result —
108
+ * it travels through the provider-owned dehydration channel (see
109
+ * `hydrationChannel.ts`).
102
110
  */
103
111
  const hydratableObserverResult = <
104
112
  TQueryFnData,
@@ -107,7 +115,7 @@ const hydratableObserverResult = <
107
115
  TQueryKey extends QueryKey,
108
116
  TDataHydratable,
109
117
  >(
110
- query: Query<TQueryFnData, TError, TData, TQueryKey>,
118
+ _query: Query<TQueryFnData, TError, TData, TQueryKey>,
111
119
  result: QueryObserverResult<TDataHydratable, TError>,
112
120
  ) => {
113
121
  if (!isServer) return result
@@ -124,15 +132,6 @@ const hydratableObserverResult = <
124
132
  obj.fetchPreviousPage = undefined
125
133
  }
126
134
 
127
- // We will also attach the dehydrated state of the query to the result
128
- // This will be removed on client after hydration
129
- obj.hydrationData = {
130
- state: query.state,
131
- queryKey: query.queryKey,
132
- queryHash: query.queryHash,
133
- ...(query.meta && { meta: query.meta }),
134
- }
135
-
136
135
  return obj
137
136
  }
138
137
 
@@ -281,6 +280,46 @@ export function useBaseQuery<
281
280
  let unsubscribe: (() => void) | null = null
282
281
  let disposed = false
283
282
 
283
+ /**
284
+ * Attach the client subscriber for a component that hydrated from SSR
285
+ * output.
286
+ *
287
+ * During hydration Solid replays the `queryResource` memo below from the
288
+ * serialized SSR value with `Promise` mocked, so the promise executor
289
+ * that normally creates the client subscriber never runs (nor could it:
290
+ * a mount refetch started from inside the replay would never settle).
291
+ * The replay is detected without touching any internals: a real
292
+ * `Promise` runs its executor synchronously, the hydration mock does
293
+ * not, so `executorRan` stays false exactly when this compute was
294
+ * replayed.
295
+ *
296
+ * The subscription is coordinated with the provider's dehydration
297
+ * channel: it attaches once this query's entry has been primed into the
298
+ * cache (or once the channel completes without one), so mount semantics
299
+ * see the hydrated cache state — a still-fresh query does not refetch, a
300
+ * stale one does, and cache writes that landed earlier are reconciled at
301
+ * attach. The wait is per-query, not global-hydration-end: a component
302
+ * hydrated from an early flush goes live while later boundaries are
303
+ * still streaming, so it is not deaf to cache writes, is seen by
304
+ * invalidateQueries' active-query refetch, and cannot be gc'ed while
305
+ * visible. Without a provider (manual `queryClient` option) it falls
306
+ * back to a plain microtask.
307
+ */
308
+ const coordinator = useContext(HydrationCoordinatorContext)
309
+ const attachHydratedSubscriber = () => {
310
+ if (!unsubscribe && !disposed && !isRestoring()) {
311
+ unsubscribe = createClientSubscriber()
312
+ }
313
+ }
314
+ const scheduleHydratedAttach = () => {
315
+ const queryHash = untrack(() => observer.getCurrentQuery().queryHash)
316
+ if (coordinator) {
317
+ coordinator.whenQueryPrimed(queryHash, attachHydratedSubscriber)
318
+ } else {
319
+ queueMicrotask(attachHydratedSubscriber)
320
+ }
321
+ }
322
+
284
323
  /*
285
324
  Fixes #7275
286
325
  In a few cases, the observer could unmount before the resource is loaded.
@@ -325,7 +364,9 @@ export function useBaseQuery<
325
364
  }
326
365
  }
327
366
 
328
- return new Promise<ResourceData>((resolve, reject) => {
367
+ const replayProbe = { executorRan: false }
368
+ const resource = new Promise<ResourceData>((resolve, reject) => {
369
+ replayProbe.executorRan = true
329
370
  resolver = resolve
330
371
  if (isServer) {
331
372
  unsubscribe = createServerSubscriber((data) => {
@@ -376,6 +417,15 @@ export function useBaseQuery<
376
417
  )
377
418
  }
378
419
  })
420
+
421
+ if (!isServer && !replayProbe.executorRan) {
422
+ // Hydration replay: `Promise` was mocked and the executor above never
423
+ // ran, so no subscriber was created. Schedule the attach through the
424
+ // provider's hydration coordinator (see scheduleHydratedAttach).
425
+ scheduleHydratedAttach()
426
+ }
427
+
428
+ return resource
379
429
  })
380
430
 
381
431
  onCleanup(() => {