Repository navigation
Conversation
More templates
@orpc/ai-sdk
@orpc/arktype
@orpc/bun
@orpc/client
@orpc/cloudflare
@orpc/contract
@orpc/effect
@orpc/evlog
@orpc/hibernation
@orpc/json-schema
@orpc/experimental-lock
@orpc/mcp
@orpc/msw
@orpc/nest
@orpc/next
@orpc/node
@orpc/openapi
@orpc/opentelemetry
@orpc/pinia-colada
@orpc/pino
@orpc/publisher
@orpc/ratelimit
@orpc/server
@orpc/shared
@orpc/swr
@orpc/tanstack-query
@orpc/trpc
@orpc/valibot
@orpc/zod
commit: |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — @orpc/tanstack-query SSR placeholder fix, verified against @tanstack/query-core@5.105.0 internals; the affected suites pass locally (pnpm vitest run packages/tanstack-query/src/stream-query.test.ts packages/tanstack-query/src/ssr.test.ts → 37 passed).
serializableStreamedQuerydistinguishesinitialDatafrom fetched data —isInitialDatakeys offquery.state.dataUpdateCount === 0(TanStack keeps this at0forinitialDataand bumps it on every successfulsetData), so'reset'no longer clears the placeholder to pending and'replace'streams chunks instead of buffering the first fetch.- Connect-time re-check of live state — the first
setQueryDataupdater readsquery.state.dataUpdateCountat call time, so asetQueryDatalanding while the stream is connecting is preserved rather than wiped, while non-appendmodes still start the first fetch from[]. StreamQuerySSRUtilsPlugindates the placeholderinitialDataUpdatedAt: 0— the empty placeholder is always stale, sohydrateoverwrites it (hydrateonly replaces whenstate.dataUpdatedAt > query.state.dataUpdatedAt); without it,initialDatawould be datedDate.now()at browser build time.- Docs/JSDoc updated to state that
initialDatais not fetched data. - Tests — per-mode unit tests (initial data, overwrite, write-during-connect) and a browser
QueryObserverper mode, plus a hydrate-replaces-placeholder test; the existing server-sidecancelOnFirstDatatests still pass.
One non-blocking note: users who pass their own initialData to .streamedOptions now keep it visible through the first 'reset'/'replace' refetch instead of returning to pending — intentional and documented here. The near-identical serializableStreamedQuery in @orpc/pinia-colada still conflates initialData with previous data, but that package has no SSR placeholder plugin, so it is out of scope for this PR.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Merging this PR will not alter performance1 warning
Comparing Footnotes
|
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — the delta since the prior pullfrog review (e048b21), re-verified against @tanstack/query-core@5.105.0 internals; the affected suites pass locally (pnpm vitest run packages/tanstack-query/src/stream-query.test.ts packages/tanstack-query/src/ssr.test.ts → 37 passed) and eslint is clean on the changed files.
serializableStreamedQueryinitial-data handling simplified — the separateisInitialDataflag is gone;hasPreviousDatanow requiresquery.state.dataUpdateCount > 0, so the placeholderinitialDatais simply "not previous data". The'reset'-to-pending branch,shouldUpdateCacheDuringStream, and the connect-timesetQueryDataall fall out of that single predicate. Range-diffed againste048b21: equivalent for every mode and case.- Connect-time live read preserved — the first
setQueryDataupdater (refetchMode === 'append' || query?.state.dataUpdateCount ? limitArraySize(prev) : []) still reads the livedataUpdateCount, so a write landing while the stream connects is kept and non-appendmodes start the first fetch empty. StreamQuerySSRUtilsPluginplaceholder dated0—initialDataUpdatedAt: 0keeps the placeholder always stale sohydratereplaces it; the verbose JSDoc was collapsed to a one-line comment.- Tests —
stream-query.test.tsgainedwithInitialData/fetchStreamhelpers;ssr.test.tstightened its scheduled assertions (intermediate streaming values are still covered by the sibling browser test). All still green.
One non-blocking note carried from the prior review: users who pass their own initialData to .streamedOptions now keep it visible through the first 'reset'/'replace' refetch instead of returning to pending — intentional and documented here. The near-identical serializableStreamedQuery in @orpc/pinia-colada still conflates initialData with previous data, but that package has no SSR placeholder plugin, so it is out of scope for this PR.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
orpc | 75a3d73 | Commit Preview URL Branch Preview URL |
Nov 04 2026, 03:43 PM |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — the delta since the prior pullfrog review (ad63237), traced against @tanstack/query-core@5.105.0; the affected suites pass (pnpm vitest run packages/tanstack-query/src/stream-query.test.ts packages/tanstack-query/src/ssr.test.ts → 41 passed) and eslint is clean on the changed files.
- Preserved the server's data update count for streamed queries —
cancelOnFirstDatawas split intocreateCancelOnFirstData(keepDataUpdateCount); streamed queries now restore thedataUpdateCountcaptured at intercept time after cancelling, so the dehydrated snapshot of a placeholder-only streamed query stays initial data and the browser's mount refetch no longer resets it to pending. - Kept live-query cancellation unchanged — live queries use the
keepDataUpdateCount: falsevariant, so the existing first-event revert behavior (and its data) is untouched. - Covered the hydration round trip — new tests reopen the stream after server fetch → dehydrate → hydrate without a pending state in all three refetch modes, and assert a query with pre-connect data keeps its update count.
- Documented the rule — docs and the
refetchModeJSDoc now state thatinitialDatais not fetched data.
I traced the ordering the new interceptor relies on: query-core's cancel({ revert: true }) runs onCancel synchronously (setState({ ...#revertState, fetchStatus: 'idle' })), and because setQueryData is manual: true the revert keeps the connect/stream data, so the follow-up setState only pins the count. Interceptors wrap queryFn, so the query exists and state.dataUpdateCount is a number whenever the subscriber can match it.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — the delta since the prior pullfrog review (016dd260), in which the branch was rewritten from the dataUpdateCount/initialData fix into the snapshot-based redesign now described in the PR body. I verified the new mechanics against @tanstack/query-core@5.105.0 and @pinia/colada@1.4.8; pnpm vitest run packages/tanstack-query packages/pinia-colada packages/swr packages/shared → 802 passed, pnpm type:check → exit 0, eslint clean on the changed sources.
- Extracted a shared stream-to-cache pipeline —
pipeStreamToCachein@orpc/sharednow owns the connect write, per-chunk writes (reading the cache live), abort handling, and the newsettlehook for tanstack, pinia, and swr alike, so a superseded fetch writes nothing and a generator that is no longer wanted never starts. - Redefined
refetchModebefore the connect —'replace'is removed in all three integrations;'reset'replaces existing data with[]at connect and'append'keeps it trimmed tomaxChunks, and a query never returns topendingonce it has data. - Added the per-call
snapshotoption — settles a fetch at connect (streamed) or on the first event (live), closes the stream, keeps the data as a stale snapshot, and stays out of the query key; TanStack settles throughquery.cancel({ revert: true })plus aninvalidateOnAbortlistener, pinia throughentry.active. - Reimplemented the TanStack
StreamQuerySSRUtilsPluginas a snapshot-only plugin —trueon the server and'unobserved'in the browser; theinitialData,initialDataUpdatedAt,refetchOnMount: 'always'defaults and thecreateCancelOnFirstData/dataUpdateCountworkaround are deleted. - Added the pinia-colada SSR plugin — same snapshot policy plus
staleTime: 0, exported from@orpc/pinia-colada, with arenderToString→ serialize → hydrate → remount end-to-end test. - Moved
limitArraySizeinto@orpc/shared— the three integrations now share one trimming helper. - Rewrote the docs — the
refetchModenotes, asnapshotreference for both integrations, "Migrating fromrefetchMode: 'replace'" recipes, the SSR sections, the swr subscription notes, and the Next.js SSR blog paragraph. - Reworked the tests — shared pipeline unit tests, per-origin/per-mode TanStack suites, snapshot with and without observers, suspense and key-change cases, hydration end-to-end runs, and pinia mount/serialize coverage.
I traced the two load-bearing third-party interactions the new design depends on. In query-core, cancel() rejects the retryer first and only then runs onCancel, which restores #revertState before aborting the signal, so the snapshot written at connect survives; Query.fetch returns that data on CancelledError{ revert } before dispatching an error, the post-cancel rejection of the query function is swallowed, and invalidate() does not trigger a refetch by itself — so no 'error' state, lost chunk, unhandled rejection, or refetch loop. In colada, a supersede aborts the previous call before the new query function runs and guards the completion write on pendingCall === entry.pending, while direct entry.state.value writes never advance entry.when, so they can only over-report staleness (never suppress the reopen).
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No critical issues — one docs consistency nit inline.
Reviewed changes — the delta since the prior pullfrog review (b58942b1), a refactor commit that shares the streamed-array writes across integrations and deduplicates tests. Re-ran pnpm vitest run packages/shared packages/tanstack-query packages/pinia-colada packages/swr (55 files / 786 tests), pnpm type:check and eslint on the changed sources; all pass.
- Extracted
streamedArrayWrites/StreamedArrayOptionsinto@orpc/shared— tanstack, pinia and swr now build their connect/chunk writes from one helper (onConnecttrims kept data in'append',onChunkappends and trims), and their option types alias the shared one. - Merged the tanstack snapshot hooks into
trackStream— the abort-to-invalidate()listener is now registered by the same function that returns the settle hook, so a settled, cancelled, or orphaned stream is marked stale in one place. - Removed tanstack's final
limitArraySize—serializableStreamedQueryreturns the cache verbatim, so asetQueryDatalanding after the last chunk is no longer trimmed tomaxChunks; this aligns with pinia and is asserted by the updatedmaxChunkstest. - Consolidated test scaffolding —
createChunkController/createQueryStore/createQueryCachelive inpinia-colada/tests/__tests__/utils.ts(with a realclose()), and the reset/append cases were folded intoit.eachtables across the swr, tanstack and pinia suites. - Tightened SSR docs and JSDoc — the tanstack SSR caveats moved into the
:::warningblock, per-call override wording simplified, and the plugin JSDoc reduced to the snapshot policy.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏
6f620f8 to
001a80a
Compare
8d08b30 to
ab7d545
Compare
There was a problem hiding this comment.
ℹ️ No critical issues — the docs rewrite is accurate; one migration-guidance gap inline.
Reviewed changes — the delta since the prior pullfrog review (6f620f87), which is documentation only (5 files, +94/−123): the streamed/live query and SSR prose was rewritten into terser form, tables and all. I cross-checked every behavioral claim against the code at this head (streamedArrayWrites, pinia stream-to-cache writes, both StreamQuerySSRUtilsPlugin implementations), against @pinia/colada@1.4.8 (fetch() aborts entry.pending, so refetchOnMount: 'always' does restart an open stream as the new pinia note says), and against origin/main's swr subscriber (next(undefined, undefined) before connect, so the undefined → [] migration note is correct). pnpm docs:validate → 0 errors, 0 warnings, no broken links; all #snapshots / #default-options / #streamed-query-options anchors resolve.
- Compressed the
refetchModereference into tables — tanstack, pinia and swr now state that existing data stays until connect and then tabulate'reset'→[]versus'append'→ kept (trimmed tomaxChunks), which matchesstreamedArrayWrites. - Trimmed the
snapshotsections — pinia gained atrue/'unobserved'/falsetable, tanstack moved the two caveats (not part of the query key, ignored with a customqueryFn) and the "cut-short stream is marked stale" note into bullets. - Rewrote both SSR sections — pinia now documents the plugin's
snapshotandstaleTime: 0defaults in a server/browser table (matchingpackages/pinia-colada/src/ssr.ts), and tanstack replaced prose with per-environment bullets plus a tightened:::warninglist. - Consolidated the
refetchMode: 'replace'migration intofrom-v1.mdx— the three per-page<Expandable>blocks were removed and replaced by one entry under Integrations, alongside new notes on'reset'no longer pending and swr's[]-instead-of-undefinedchange. - Tightened the Next.js SSR blog section — the two SSR problems and the plugin paragraph were shortened without changing their claims.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏
ab7d545 to
228c7e8
Compare
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — the delta since the prior pullfrog review (ab7d5450): 26 files, +176/−172. Verified with pnpm vitest run packages/shared packages/tanstack-query packages/pinia-colada packages/swr (55 files / 790 tests), pnpm type:check (exit 0) and pnpm docs:validate (strict, 0 errors / 0 warnings).
- Renamed the new option
snapshot→settleOnFirstData— code, types (StreamSnapshotOptions→SettleOnFirstDataOptions),src/snapshot.ts→src/settle-on-first-data.ts,createSnapshotSettle→createSettle, tests and docs. Headings and anchors were updated to#settle-on-first-data; a case-insensitive grep found no leftoversnapshotreferences in the affected packages orapps/content. - Stopped
settleOnFirstDataleaking into TanStack query options —.streamedOptionsand.liveOptionsnow destructure it out before spreadingoptionsIn, matching the documented "not passed on to the query options"; pinia already excluded it viarestOptions. - Docs updated consistently — both integration pages now describe
settleOnFirstData, and the SSR tables link to the renamed section.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏
228c7e8 to
b092ce7
Compare
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — the delta since the prior pullfrog review (228c7e89), which is a key-composition change for streamed queries: 7 files, +38/−4. Verified with pnpm vitest run packages/tanstack-query packages/pinia-colada packages/shared packages/swr (55 files / 792 tests), pnpm type:check (exit 0), pnpm docs:validate (strict, 0 errors / 0 warnings) and eslint on the changed sources.
- Excluded
refetchModefrom the streamed query key —generateOperationKeyin@orpc/tanstack-queryand@orpc/pinia-coladanow emits onlyfnOptions: { maxChunks }instead of the wholefnOptions, so streamed queries that differ only inrefetchModeshare one cache entry and one stream, andstreamedKey({ input })now matches a streamed query with nomaxChunksexactly. - Kept
maxChunksin the key — different values still yield separate entries and streams, asserted by the updatedkey.test.tscases in both packages and a new.streamedOptions/.streamedKeyequivalence test in tanstack. - Documented the key composition — new
:::warningblocks on both integration pages explain themaxChunkssplit and the option name eachstreamedKeytakes (queryFnOptionsfor tanstack,fnOptionsfor pinia), which matchesStreamedKeyOptionsin each package.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No critical issues — one minor test-coverage gap inline.
Reviewed changes — the delta since the prior pullfrog review (539ae9f8): 23 files, +115/−155, a refactor commit that extracts the shared key projection and simplifies tests and docs. Verified with pnpm vitest run packages/shared packages/tanstack-query packages/pinia-colada packages/swr (55 files / 789 tests, −3 from the merged/removed cases) and pnpm type:check (exit 0).
- Extracted
streamedArrayKeyOptionsinto@orpc/shared— bothgenerateOperationKeyimplementations now spread its{ maxChunks } | undefinedresult. Behavior is unchanged: the removed inline conditionals already emitted onlymaxChunkswhen present and nothing when absent. New unit tests cover the present/absent/undefined branches, and the shared export is asserted inindex.test.ts. - TanStack
liveQuerynow resolves with the cache value —getQueryData(key) ?? result.datainstead of the last chunk, so asetQueryDatalanding after the final event survives. This matchesserializableStreamedQueryand pinia'sentry.state.value.data, and is the delta's only behavior change. - Test consolidation —
distinctDatamoved to a shared tanstack test util (now usingstringifyJSON), pinia's duplicate "writes survive the stream" cases and SSR plugin cases merged intoit.each, and redundant TanStack SSR assertions dropped. - Docs — compressed the
refetchMode/settleOnFirstData/SSR prose and dropped the "writes during the stream are kept" sentence that only existed on an intermediate commit.
I checked that the dropped TanStack SSR assertions were genuinely redundant: onSuccess not firing for settled fetches is still asserted in stream-query.test.ts:294, and "generator never starts" is covered in shared/src/stream-to-cache.test.ts:126,242. The removed "shares one key across refetch modes" integration test is likewise covered by key.test.ts.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved issues allow retries to accumulate abort listeners, cancellation during connection to leave streams closed, and stream completion to overwrite newer null cache values.
3 open issues
What changed in this PR
This PR unifies streamed-query behavior across oRPC’s TanStack Query, Pinia Colada, and SWR integrations, with revised refetch and server-rendering semantics.
Changes:
- Introduces a shared stream-to-cache implementation, preserves existing data until connection, and removes
'replace'mode. - Adds
settleOnFirstDatafor TanStack Query and Pinia Colada, simplifies the TanStack SSR plugin, and introduces the Pinia Colada equivalent. - Expands lifecycle, hydration, and type coverage, and updates integration and migration documentation.
| File | Description |
|---|---|
| packages/tanstack-query/tests/__shared__/utils.ts | Adds a helper for comparing data transitions. |
| packages/tanstack-query/src/types.ts | Adds streamed and live settlement option types. |
| packages/tanstack-query/src/stream-query.ts | Adopts shared streaming and settlement logic. |
| packages/tanstack-query/src/stream-query.test.ts | Tests refetch, cancellation, and settlement behavior. |
| packages/tanstack-query/src/ssr.ts | Simplifies the plugin to configure settlement. |
| packages/tanstack-query/src/ssr.test.ts | Covers settlement, remounting, and hydration. |
| packages/tanstack-query/src/settle-on-first-data.ts | Adds settlement and abort invalidation tracking. |
| packages/tanstack-query/src/procedure-utils.ts | Forwards settlement options to query functions. |
| packages/tanstack-query/src/procedure-utils.test.ts | Verifies settlement option forwarding. |
| packages/tanstack-query/src/procedure-utils.test-d.ts | Checks new options and removed refetch mode. |
| packages/tanstack-query/src/live-query.ts | Uses shared streaming and preserves cache writes. |
| packages/tanstack-query/src/live-query.test.ts | Covers live-query settlement and existing data. |
| packages/tanstack-query/src/key.ts | Excludes refetch mode from generated keys. |
| packages/tanstack-query/src/key.test.ts | Verifies streamed key normalization. |
| packages/tanstack-query/src/index.ts | Exports settlement option types. |
| packages/swr/tests/e2e.test.tsx | Covers reset and append on resubscription. |
| packages/swr/src/types.ts | Reuses shared streamed-array options. |
| packages/swr/src/procedure-utils.ts | Moves subscriptions to the shared streaming core. |
| packages/swr/src/procedure-utils.test.ts | Tests connection writes, cancellation, and external updates. |
| packages/swr/src/procedure-utils.test-d.ts | Checks supported refetch modes. |
| packages/shared/src/stream-to-cache.ts | Introduces shared streaming and array-write utilities. |
| packages/shared/src/stream-to-cache.test.ts | Covers writes, aborts, settlement, and errors. |
| packages/shared/src/index.ts | Exports the streaming utilities. |
| packages/shared/src/index.test.ts | Checks new utility exports. |
| packages/shared/src/array.ts | Extracts the array-size limiter. |
| packages/shared/src/array.test.ts | Tests array limiting behavior. |
| packages/pinia-colada/tests/ssr.test.tsx | Tests server rendering through browser hydration. |
| packages/pinia-colada/tests/__shared__/utils.ts | Adds reusable query and stream test helpers. |
| packages/pinia-colada/src/types.ts | Adds streamed and live settlement option types. |
| packages/pinia-colada/src/stream-query.ts | Adopts shared streaming and live cache reads. |
| packages/pinia-colada/src/stream-query.test.tsx | Covers revised refetch and settlement behavior. |
| packages/pinia-colada/src/ssr.ts | Introduces the streaming SSR plugin. |
| packages/pinia-colada/src/ssr.test.ts | Tests plugin defaults and overrides. |
| packages/pinia-colada/src/settle-on-first-data.ts | Adds entry-write and settlement helpers. |
| packages/pinia-colada/src/procedure-utils.ts | Forwards settlement options to query functions. |
| packages/pinia-colada/src/procedure-utils.test.ts | Verifies settlement option forwarding. |
| packages/pinia-colada/src/procedure-utils.test-d.ts | Checks settlement types and removed refetch mode. |
| packages/pinia-colada/src/live-query.ts | Uses shared streaming and preserves entry updates. |
| packages/pinia-colada/src/live-query.test.tsx | Tests cache preservation, refetching, and settlement. |
| packages/pinia-colada/src/key.ts | Excludes refetch mode from generated keys. |
| packages/pinia-colada/src/key.test.ts | Verifies streamed key normalization. |
| packages/pinia-colada/src/index.ts | Exports the SSR plugin and settlement types. |
| packages/pinia-colada/src/index.test.ts | Checks the SSR plugin export. |
| apps/content/docs/migrations/from-v1.mdx | Documents refetch changes and a buffering alternative. |
| apps/content/docs/integrations/tanstack-query.mdx | Explains revised streaming, keys, and SSR behavior. |
| apps/content/docs/integrations/swr.mdx | Documents reset and append subscription behavior. |
| apps/content/docs/integrations/pinia-colada.mdx | Documents settlement options and SSR setup. |
| apps/content/blog/nextjs-ssr-orpc-tanstack-query.mdx | Updates the SSR plugin explanation. |
Review effort: Balanced
There was a problem hiding this comment.
Important
The new pinia-colada "mark the entry stale when its last component unmounts" logic is bypassed by @pinia/colada@1.4.9, which aborts the pending fetch itself before the next stream write — the entry stays fresh and the stream never reopens. The PR's own test for it fails against that version.
Reviewed changes — the delta since the prior pullfrog review (c324cc66): four commits that change how settled and orphaned streams resolve and are invalidated, harden the TanStack tracking against retries and early aborts, and raise the query-core peer floor.
- Settled TanStack fetches resolve instead of cancelling —
createStreamCache.settlesubscribes to the query cache and invalidates the query once its own non-manual success lands, so interceptors get a resolvednext(),onSuccess/onSettledfire, and a query dehydrated while pending hydrates with its first data. - Writes go to the fetch's own query —
query.setData(value, { manual: true })replacesclient.setQueryData, so a customqueryKeyHashFnis respected;read()plus thecached === undefinedfallback keep anullwritten after the last event, andliveQueryrejects anundefinedevent. - Abort tracking happens before connecting, once per signal —
createStreamCacheis built beforeawait queryFn(context), and a module-leveltrackedSignalsWeakSet keeps retries from stacking listeners; a cancel during connect now marks the query stale. These three were the Copilot findings onc324cc66. - pinia-colada stops orphaned streams —
createEntryStreamzeroesentry.whenand aborts the entry's pending fetch at the next write once the last component leaves. - TanStack SSR plugin sets
refetchOnMount: 'always'— hydrated data, including queries dehydrated while pending, reopens its stream on mount; both SSR plugins now also treat Deno as the server. - Shared and swr cleanups —
pipeStreamToCache.updatereturns nothing and the core captures what was stored, a throwing connect write closes the stream,maxChunks: Infinitydrops out of the streamed key, and the two swr subscribers share onestreamSubscriber. - Peer floor —
@tanstack/query-corenow requires>=5.105.0(breaking, as documented), and the docs drop the "before 5.102" caveat. - Tests and docs — new cases for retry listeners, hash functions, mid-connect aborts, null writes, pending hydration and Deno;
pnpm type:check, eslint andpnpm docs:validateare clean.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏
b13790c to
b488244
Compare
There was a problem hiding this comment.
✅ No new issues found. The pinia-colada stale-on-unmount finding from the prior review is addressed, and I found nothing new in the fix.
Reviewed changes — the delta since the prior pullfrog review (d4d1c47), which moves the pinia-colada stale marking onto the abort signal and raises the Colada floor:
entry.when = 0now happens on abort —createEntryStreamregisters a one-shot abort listener on the fetch signal instead of aborting the pending fetch from the next write, so Colada 1.4.9's ownuntrackabort marks the entry stale;updateis a plainwriteEntryDataagain and the write-time abort branch is gone.@pinia/coladafloor raised to1.4.9— peer range, dev dependency and lockfile all move together, since 1.4.9 is what aborts a stream once its last component unmounts.- The unmount test is deterministic — it waits for the first fetch to settle (
entry().pending === null), records the fetch signals, and assertssignals[1]aborted plusentry().stale, instead of racing the refetch; the mid-stream test name now matches the behavior. - Docs wording — "a stream also closes once the last component using it unmounts", which matches 1.4.9.
Verified at this head: pnpm vitest run packages/shared packages/tanstack-query packages/pinia-colada packages/swr → 55 files / 807 tests passed; pnpm docs:validate → 0 errors / 0 warnings.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏
ffdf8cf to
23d077a
Compare
There was a problem hiding this comment.
ℹ️ No critical issues — one behavioral edge in the pinia reopen path and one stale JSDoc line inline.
Reviewed changes — the delta since the prior pullfrog review (the last reviewed production state is b488244a; ffdf8cf was test-only): both settle helpers were replaced by stream-cache.ts modules, the shared stream core and SSR detection were reworked, and the four packages' suites now run 818 tests (up from 807).
- Rebuilt both settle pipelines around shared helpers —
followStream(tanstack) andfollowEntryStream(pinia) now own data writes and stale marking, and the new sharedrunWithOwnSignalgives every backend call its own abort signal, cancelled once its stream ends. - Reopened the stream for an observer that joins while it closes — TanStack invalidates once the settled fetch is stored and reopens through a
query.fetch()microtask; pinia re-follows a second stream inside the same fetch. - Rewrote
pipeStreamToCacheto drive the iterator manually — a failingiterator.return()no longer masks the stored data or the original error, and an abort reason now wins over a concurrent stream error. - Skipped unchanged writes in both
updateimplementations, so an'append'connect write no longer produces a cache notification. - Re-scoped both SSR plugins — TanStack detects the server with
environmentManager.isServer()and setsrefetchOnMount: falsethere (the browser keeps'always'); pinia now appliesstaleTime: 0in the browser only. - Trimmed the returned streamed array to
maxChunksagain in both integrations, and SWR's.liveSubscribernow passes the event value instead of a mutator. - Tests and docs — new shared iterator/
runWithOwnSignalcases, TanStack reopen and unchanged-write cases, pinia reopen cases, and the SSR/staleTime/live-undefineddocumentation updates.
Verified at this head: pnpm vitest run packages/shared packages/tanstack-query packages/pinia-colada packages/swr → 55 files / 818 passed, pnpm type:check exit 0, eslint clean on the changed sources, pnpm docs:validate 0 errors / 0 warnings.
…ch and SSR semantics All three integrations now share one streaming core in @orpc/shared (`pipeStreamToCache`, `streamedArrayWrites`, `streamedArrayKeyOptions`): - nothing is written before the stream connects, so a query with data never returns to pending; at connect 'reset' writes [] and 'append' keeps the existing data (trimmed to `maxChunks`), then each chunk is appended to the live cache value, so writes made during the stream are kept - 'replace' (buffer until the stream ends) is removed - `refetchMode` is no longer part of the query key; `maxChunks` is (except the unlimited default) - a new per-call `settleOnFirstData` option (true | 'unobserved' | false) settles a fetch at its first data: the fetch resolves with it, the stream closes, and the data is marked stale so the stream reopens when observed StreamQuerySSRUtilsPlugin sets `settleOnFirstData` (true on the server, 'unobserved' in the browser; no `window` or Deno counts as the server). The TanStack plugin also sets `refetchOnMount: 'always'`, so hydrated data, even data dehydrated while pending, reopens its stream on mount. pinia-colada gains the same plugin (with `staleTime: 0`). A stream cut short reopens when observed again: TanStack invalidates a cancelled or abandoned fetch, and Colada marks an aborted fetch's entry stale. TanStack writes go through the fetch's own query (custom `queryKeyHashFn`), a throwing connect write closes the stream, and swr's subscribers share one implementation. This fixes streamed queries suspending over the plugin's placeholder, 'replace' freezing long-lived streams, hydrated server data resetting to pending (a Suspense fallback flash), and pending-dehydrated queries hydrating into an error state. BREAKING CHANGE: `refetchMode: 'replace'` is removed. 'reset' keeps existing data until the new stream connects, then sets it to []; it no longer returns a query with data to pending. In swr, 'reset' writes [] at connect instead of undefined. Streamed query keys no longer include `refetchMode`. The TanStack SSR plugin no longer sets `initialData: []`. TanStack `liveQuery` rejects an `undefined` event. Peer ranges are now `@tanstack/query-core >=5.105.0` and `@pinia/colada >=1.4.9`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7SEdTAhZeQYD5N7KE8
23d077a to
75a3d73
Compare
There was a problem hiding this comment.
✅ No new issues found. The two findings from the prior review are addressed, and the fix is sound.
Reviewed changes — the delta since the prior pullfrog review (23d077a5): 3 hunks in @orpc/pinia-colada and @orpc/tanstack-query.
- Fixed the pinia reopen edge — phase 2 of
followEntryStreamnow skips the reopen when the fetch signal is already aborted, and(await follow(undefined)) ?? resultkeeps phase 1's settled data when the reopened stream writes nothing, so a successful live entry is no longer flipped tostatus: 'error'by an empty reopen. - Added a regression test —
live-query.test.tsxsimulates an observer joining while the settled stream closes, then asserts the fetch resolves with the settled data and the reopen ran once. - Corrected the TanStack SSR plugin JSDoc — no longer claims
refetchOnMount: 'always'unconditionally, matching the serverfalse/ browser'always'split.
The remaining behavior — awaiting the reopened stream inside the settled fetch — is unchanged and now documented as intentional by the author (a Colada query function cannot start a separate fetch for the entry, and a mounted component joining an 'unobserved' fetch should follow the stream). Focused suites pass (live-query.test.tsx, stream-query.test.tsx, tanstack-query/src/ssr.test.ts → 48 tests).
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏


Problem
Streamed queries with
StreamQuerySSRUtilsPluginmisbehaved in several ways. All of them came from asking "is this fetch a refetch?":'reset'put the query back topendingover the plugin'sinitialData: []placeholder, souseSuspenseQuerysuspended. Preventing that was the plugin's whole purpose.'replace'buffered even the first fetch, so a long-lived stream showed nothing until it ended.Date.now(), so astaleTimeblocked the fetch and hydrated data lost to it.pending, so the Suspense fallback flashed.dataUpdateCountvsisFetched()vsdata !== undefined.Design
All three integrations now run one core,
pipeStreamToCache, in@orpc/shared:'reset''append'pending.[]maxChunksThe mode only matters at connect, so nothing asks "is this a refetch?" any more.
refetchModeis not part of the query key, so queries that differ only in it share one entry and one stream.maxChunksis part of the key.Each call of the client gets its own signal, aborted once its stream is no longer read, so the request behind a settled or finished stream is told to stop.
New per-call
settleOnFirstDataoption (true | 'unobserved' | false) on.streamedOptionsand.liveOptions:staleTime: 0makes it stale. Either way, the stream reopens once the query is observed.StreamQuerySSRUtilsPluginsetssettleOnFirstData:trueon the server and'unobserved'in the browser.environmentManager.isServer(); pinia-colada treats nowindow, or Deno, as the server.refetchOnMount:'always'in the browser, so hydrated data reopens its stream on mount (including queries dehydrated while pending), andfalseon the server, so components sharing a settled query don't reopen it.staleTime: 0in the browser only.initialDataor adds interceptors any more.Streams that are cut short reopen when observed again:
Breaking changes
refetchMode: 'replace'is removed in tanstack-query, pinia-colada and swr. The v1 migration guide shows a one-line customqueryFnthat collects the stream.'reset'keeps existing data until the new stream connects, then sets it to[]. Before, it cleared the data as soon as a refetch started, which put the query back inpending. A query without data is stillpendinguntil the stream connects..subscriberwrites at connect:[]with'reset'(instead ofundefined), and the existing data trimmed tomaxChunkswith'append'.refetchMode.initialData: [].fetchQueryon a streamed query resolves at connect. PasssettleOnFirstData: falseto wait for the stream to end.refetchOnMount: trueto respectstaleTimeinstead.liveQueryrejects anundefinedevent, which TanStack Query cannot store.@tanstack/query-corepeer range is now>=5.105.0.@pinia/coladapeer range is now>=1.4.9.Tests
@orpc/shared:pipeStreamToCachecovers connect and chunk writes, live reads, abort at each point, a throwing connect write,settle, a stream that fails to close, and errors.runWithOwnSignalis covered too.pending.settleOnFirstDatawith and without an observer, including an observer arriving while the stream closes or before the fetch is stored, and no extra write for unchanged'append'data.queryKeyHashFn.renderToString→ hydrate → mount end-to-end test, and streams closing after unmount.pnpm vitest runpasses (4471+ tests).pnpm type:check,pnpm lint,pnpm docs:validateandprepack(verifyDist) for every changed package are clean.🤖 Generated with Claude Code
https://claude.ai/code/session_01VVaw7SEdTAhZeQYD5N7KE8