Skip to content

feat(pinia-colada,tanstack-query,swr)!: redesign streamed query refetch and SSR semantics - #2399

Open
dinwwwh wants to merge 1 commit into
mainfrom
claude/lucid-brahmagupta-tfpwfg
Open

dinwwwh wants to merge 1 commit into
mainfrom
claude/lucid-brahmagupta-tfpwfg

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

Problem

Streamed queries with StreamQuerySSRUtilsPlugin misbehaved in several ways. All of them came from asking "is this fetch a refetch?":

  1. Placeholder reset: 'reset' put the query back to pending over the plugin's initialData: [] placeholder, so useSuspenseQuery suspended. Preventing that was the plugin's whole purpose.
  2. Frozen first fetch: 'replace' buffered even the first fetch, so a long-lived stream showed nothing until it ended.
  3. Fresh-looking placeholder: the placeholder was dated Date.now(), so a staleTime blocked the fetch and hydrated data lost to it.
  4. Fallback after hydration: the browser's refetch on mount reset the hydrated server data to pending, so the Suspense fallback flashed.
  5. Fragile refetch check: the answer depended on dataUpdateCount vs isFetched() vs data !== undefined.
  6. Diverging integrations: pinia-colada and swr each had their own version of this logic.

Design

All three integrations now run one core, pipeStreamToCache, in @orpc/shared:

Moment 'reset' 'append'
Fetch start until connect No write. Existing data stays visible, so a query with data never returns to pending. Same
Connect Write [] Keep the existing data, trimmed to maxChunks
Each chunk Appended to the live cache value, so writes made during the stream are kept Same
Abort No further writes. The stream is closed, and a generator that hasn't started never starts. Same

The mode only matters at connect, so nothing asks "is this a refetch?" any more. refetchMode is not part of the query key, so queries that differ only in it share one entry and one stream. maxChunks is 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 settleOnFirstData option (true | 'unobserved' | false) on .streamedOptions and .liveOptions:

  • It settles a fetch at its first data: when a streamed query connects, or on a live query's first event.
  • The fetch resolves with that data, and the stream is closed.
  • In TanStack, the data is marked stale. In Colada, the plugin's staleTime: 0 makes it stale. Either way, the stream reopens once the query is observed.
  • If an observer joins the fetch while the stream closes, the stream reopens for it.
  • It is not part of the query key, and it is not passed on to the query options.

StreamQuerySSRUtilsPlugin sets settleOnFirstData: true on the server and 'unobserved' in the browser.

  • TanStack detects the server with environmentManager.isServer(); pinia-colada treats no window, or Deno, as the server.
  • The TanStack plugin also sets refetchOnMount: 'always' in the browser, so hydrated data reopens its stream on mount (including queries dehydrated while pending), and false on the server, so components sharing a settled query don't reopen it.
  • pinia-colada gets the same plugin, with staleTime: 0 in the browser only.
  • Neither plugin sets initialData or adds interceptors any more.

Streams that are cut short reopen when observed again:

  • TanStack: a cancelled stream, or one left by its last observer, is marked invalidated.
  • Colada: Colada (1.4.9+) aborts the stream once the last component unmounts, and the aborted fetch marks its entry stale.

Breaking changes

  1. refetchMode: 'replace' is removed in tanstack-query, pinia-colada and swr. The v1 migration guide shows a one-line custom queryFn that collects the stream.
  2. '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 in pending. A query without data is still pending until the stream connects.
  3. In swr, .subscriber writes at connect: [] with 'reset' (instead of undefined), and the existing data trimmed to maxChunks with 'append'.
  4. Streamed query keys no longer include refetchMode.
  5. The TanStack SSR plugin no longer sets initialData: [].
    • With the plugin, browser fetchQuery on a streamed query resolves at connect. Pass settleOnFirstData: false to wait for the stream to end.
    • A finished finite stream reruns on each mount. Pass refetchOnMount: true to respect staleTime instead.
  6. TanStack liveQuery rejects an undefined event, which TanStack Query cannot store.
  7. The client is called with its own signal (following the query's), not the query's signal itself.
  8. The @tanstack/query-core peer range is now >=5.105.0.
  9. The @pinia/colada peer range is now >=1.4.9.

Tests

  • @orpc/shared: pipeStreamToCache covers connect and chunk writes, live reads, abort at each point, a throwing connect write, settle, a stream that fails to close, and errors. runWithOwnSignal is covered too.
  • tanstack-query:
    • Both refetch modes never return a query with data to pending.
    • settleOnFirstData with 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.
    • SSR end to end, including dehydration while pending, interceptors around a settled fetch, and a custom queryKeyHashFn.
  • pinia-colada: the same semantics, the new plugin with a renderToString → hydrate → mount end-to-end test, and streams closing after unmount.
  • swr: both modes, with unsubscribe and StrictMode still covered.
  • Checks: full pnpm vitest run passes (4471+ tests). pnpm type:check, pnpm lint, pnpm docs:validate and prepack (verifyDist) for every changed package are clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VVaw7SEdTAhZeQYD5N7KE8

@pkg-pr-new

pkg-pr-new Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
More templates

@orpc/ai-sdk

npm i https://pkg.pr.new/@orpc/ai-sdk@2399

@orpc/arktype

npm i https://pkg.pr.new/@orpc/arktype@2399

@orpc/bun

npm i https://pkg.pr.new/@orpc/bun@2399

@orpc/client

npm i https://pkg.pr.new/@orpc/client@2399

@orpc/cloudflare

npm i https://pkg.pr.new/@orpc/cloudflare@2399

@orpc/contract

npm i https://pkg.pr.new/@orpc/contract@2399

@orpc/effect

npm i https://pkg.pr.new/@orpc/effect@2399

@orpc/evlog

npm i https://pkg.pr.new/@orpc/evlog@2399

@orpc/hibernation

npm i https://pkg.pr.new/@orpc/hibernation@2399

@orpc/json-schema

npm i https://pkg.pr.new/@orpc/json-schema@2399

@orpc/experimental-lock

npm i https://pkg.pr.new/@orpc/experimental-lock@2399

@orpc/mcp

npm i https://pkg.pr.new/@orpc/mcp@2399

@orpc/msw

npm i https://pkg.pr.new/@orpc/msw@2399

@orpc/nest

npm i https://pkg.pr.new/@orpc/nest@2399

@orpc/next

npm i https://pkg.pr.new/@orpc/next@2399

@orpc/node

npm i https://pkg.pr.new/@orpc/node@2399

@orpc/openapi

npm i https://pkg.pr.new/@orpc/openapi@2399

@orpc/opentelemetry

npm i https://pkg.pr.new/@orpc/opentelemetry@2399

@orpc/pinia-colada

npm i https://pkg.pr.new/@orpc/pinia-colada@2399

@orpc/pino

npm i https://pkg.pr.new/@orpc/pino@2399

@orpc/publisher

npm i https://pkg.pr.new/@orpc/publisher@2399

@orpc/ratelimit

npm i https://pkg.pr.new/@orpc/ratelimit@2399

@orpc/server

npm i https://pkg.pr.new/@orpc/server@2399

@orpc/shared

npm i https://pkg.pr.new/@orpc/shared@2399

@orpc/swr

npm i https://pkg.pr.new/@orpc/swr@2399

@orpc/tanstack-query

npm i https://pkg.pr.new/@orpc/tanstack-query@2399

@orpc/trpc

npm i https://pkg.pr.new/@orpc/trpc@2399

@orpc/valibot

npm i https://pkg.pr.new/@orpc/valibot@2399

@orpc/zod

npm i https://pkg.pr.new/@orpc/zod@2399

commit: 75a3d73

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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).

  • serializableStreamedQuery distinguishes initialData from fetched data — isInitialData keys off query.state.dataUpdateCount === 0 (TanStack keeps this at 0 for initialData and bumps it on every successful setData), 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 setQueryData updater reads query.state.dataUpdateCount at call time, so a setQueryData landing while the stream is connecting is preserved rather than wiped, while non-append modes still start the first fetch from [].
  • StreamQuerySSRUtilsPlugin dates the placeholder initialDataUpdatedAt: 0 — the empty placeholder is always stale, so hydrate overwrites it (hydrate only replaces when state.dataUpdatedAt > query.state.dataUpdatedAt); without it, initialData would be dated Date.now() at browser build time.
  • Docs/JSDoc updated to state that initialData is not fetched data.
  • Tests — per-mode unit tests (initial data, overwrite, write-during-connect) and a browser QueryObserver per mode, plus a hydrate-replaces-placeholder test; the existing server-side cancelOnFirstData tests 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.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@codecov

codecov Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed

codspeed Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

1 warning

⚠️ 5 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

✅ 28 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing claude/lucid-brahmagupta-tfpwfg (75a3d73) with main (52a3ca1)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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.

  • serializableStreamedQuery initial-data handling simplified — the separate isInitialData flag is gone; hasPreviousData now requires query.state.dataUpdateCount > 0, so the placeholder initialData is simply "not previous data". The 'reset'-to-pending branch, shouldUpdateCacheDuringStream, and the connect-time setQueryData all fall out of that single predicate. Range-diffed against e048b21: equivalent for every mode and case.
  • Connect-time live read preserved — the first setQueryData updater (refetchMode === 'append' || query?.state.dataUpdateCount ? limitArraySize(prev) : []) still reads the live dataUpdateCount, so a write landing while the stream connects is kept and non-append modes start the first fetch empty.
  • StreamQuerySSRUtilsPlugin placeholder dated 0 — initialDataUpdatedAt: 0 keeps the placeholder always stale so hydrate replaces it; the verbose JSDoc was collapsed to a one-line comment.
  • Tests — stream-query.test.ts gained withInitialData/fetchStream helpers; ssr.test.ts tightened 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.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

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

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 — cancelOnFirstData was split into createCancelOnFirstData(keepDataUpdateCount); streamed queries now restore the dataUpdateCount captured 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: false variant, 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 refetchMode JSDoc now state that initialData is 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.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

@dinwwwh dinwwwh changed the title fix(tanstack-query): keep StreamQuerySSRUtilsPlugin's placeholder from resetting streamed queries feat!: redesign streamed query refetch and SSR semantics Nov 4, 2026

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 — pipeStreamToCache in @orpc/shared now owns the connect write, per-chunk writes (reading the cache live), abort handling, and the new settle hook for tanstack, pinia, and swr alike, so a superseded fetch writes nothing and a generator that is no longer wanted never starts.
  • Redefined refetchMode before the connect — 'replace' is removed in all three integrations; 'reset' replaces existing data with [] at connect and 'append' keeps it trimmed to maxChunks, and a query never returns to pending once it has data.
  • Added the per-call snapshot option — 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 through query.cancel({ revert: true }) plus an invalidateOnAbort listener, pinia through entry.active.
  • Reimplemented the TanStack StreamQuerySSRUtilsPlugin as a snapshot-only plugin — true on the server and 'unobserved' in the browser; the initialData, initialDataUpdatedAt, refetchOnMount: 'always' defaults and the createCancelOnFirstData/dataUpdateCount workaround are deleted.
  • Added the pinia-colada SSR plugin — same snapshot policy plus staleTime: 0, exported from @orpc/pinia-colada, with a renderToString → serialize → hydrate → remount end-to-end test.
  • Moved limitArraySize into @orpc/shared — the three integrations now share one trimming helper.
  • Rewrote the docs — the refetchMode notes, a snapshot reference for both integrations, "Migrating from refetchMode: '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).

Pullfrog  | View workflow run | Using deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ 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/StreamedArrayOptions into @orpc/shared — tanstack, pinia and swr now build their connect/chunk writes from one helper (onConnect trims kept data in 'append', onChunk appends 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 — serializableStreamedQuery returns the cache verbatim, so a setQueryData landing after the last chunk is no longer trimmed to maxChunks; this aligns with pinia and is asserted by the updated maxChunks test.
  • Consolidated test scaffolding — createChunkController/createQueryStore/createQueryCache live in pinia-colada/tests/__tests__/utils.ts (with a real close()), and the reset/append cases were folded into it.each tables across the swr, tanstack and pinia suites.
  • Tightened SSR docs and JSDoc — the tanstack SSR caveats moved into the :::warning block, per-call override wording simplified, and the plugin JSDoc reduced to the snapshot policy.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

@dinwwwh
dinwwwh force-pushed the claude/lucid-brahmagupta-tfpwfg branch from 6f620f8 to 001a80a Compare November 4, 2026 07:30
@dinwwwh dinwwwh changed the title feat!: redesign streamed query refetch and SSR semantics feat(pinia-colada,tanstack-query,swr)!: redesign streamed query refetch and SSR semantics Nov 4, 2026
@dinwwwh
dinwwwh force-pushed the claude/lucid-brahmagupta-tfpwfg branch 2 times, most recently from 8d08b30 to ab7d545 Compare November 4, 2026 08:33

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ 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 refetchMode reference into tables — tanstack, pinia and swr now state that existing data stays until connect and then tabulate 'reset' → [] versus 'append' → kept (trimmed to maxChunks), which matches streamedArrayWrites.
  • Trimmed the snapshot sections — pinia gained a true / 'unobserved' / false table, tanstack moved the two caveats (not part of the query key, ignored with a custom queryFn) and the "cut-short stream is marked stale" note into bullets.
  • Rewrote both SSR sections — pinia now documents the plugin's snapshot and staleTime: 0 defaults in a server/browser table (matching packages/pinia-colada/src/ssr.ts), and tanstack replaced prose with per-environment bullets plus a tightened :::warning list.
  • Consolidated the refetchMode: 'replace' migration into from-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-undefined change.
  • Tightened the Next.js SSR blog section — the two SSR problems and the plugin paragraph were shortened without changing their claims.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

@dinwwwh
dinwwwh force-pushed the claude/lucid-brahmagupta-tfpwfg branch from ab7d545 to 228c7e8 Compare November 4, 2026 08:58

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 leftover snapshot references in the affected packages or apps/content.
  • Stopped settleOnFirstData leaking into TanStack query options — .streamedOptions and .liveOptions now destructure it out before spreading optionsIn, matching the documented "not passed on to the query options"; pinia already excluded it via restOptions.
  • Docs updated consistently — both integration pages now describe settleOnFirstData, and the SSR tables link to the renamed section.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

@dinwwwh
dinwwwh force-pushed the claude/lucid-brahmagupta-tfpwfg branch from 228c7e8 to b092ce7 Compare November 4, 2026 09:08

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 refetchMode from the streamed query key — generateOperationKey in @orpc/tanstack-query and @orpc/pinia-colada now emits only fnOptions: { maxChunks } instead of the whole fnOptions, so streamed queries that differ only in refetchMode share one cache entry and one stream, and streamedKey({ input }) now matches a streamed query with no maxChunks exactly.
  • Kept maxChunks in the key — different values still yield separate entries and streams, asserted by the updated key.test.ts cases in both packages and a new .streamedOptions / .streamedKey equivalence test in tanstack.
  • Documented the key composition — new :::warning blocks on both integration pages explain the maxChunks split and the option name each streamedKey takes (queryFnOptions for tanstack, fnOptions for pinia), which matches StreamedKeyOptions in each package.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ 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 streamedArrayKeyOptions into @orpc/shared — both generateOperationKey implementations now spread its { maxChunks } | undefined result. Behavior is unchanged: the removed inline conditionals already emitted only maxChunks when present and nothing when absent. New unit tests cover the present/absent/undefined branches, and the shared export is asserted in index.test.ts.
  • TanStack liveQuery now resolves with the cache value — getQueryData(key) ?? result.data instead of the last chunk, so a setQueryData landing after the final event survives. This matches serializableStreamedQuery and pinia's entry.state.value.data, and is the delta's only behavior change.
  • Test consolidation — distinctData moved to a shared tanstack test util (now using stringifyJSON), pinia's duplicate "writes survive the stream" cases and SSR plugin cases merged into it.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.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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 settleOnFirstData for 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

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.settle subscribes to the query cache and invalidates the query once its own non-manual success lands, so interceptors get a resolved next(), onSuccess/onSettled fire, and a query dehydrated while pending hydrates with its first data.
  • Writes go to the fetch's own query — query.setData(value, { manual: true }) replaces client.setQueryData, so a custom queryKeyHashFn is respected; read() plus the cached === undefined fallback keep a null written after the last event, and liveQuery rejects an undefined event.
  • Abort tracking happens before connecting, once per signal — createStreamCache is built before await queryFn(context), and a module-level trackedSignals WeakSet keeps retries from stacking listeners; a cancel during connect now marks the query stale. These three were the Copilot findings on c324cc66.
  • pinia-colada stops orphaned streams — createEntryStream zeroes entry.when and 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.update returns nothing and the core captures what was stored, a throwing connect write closes the stream, maxChunks: Infinity drops out of the streamed key, and the two swr subscribers share one streamSubscriber.
  • Peer floor — @tanstack/query-core now 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 and pnpm docs:validate are clean.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

@dinwwwh
dinwwwh force-pushed the claude/lucid-brahmagupta-tfpwfg branch from b13790c to b488244 Compare November 4, 2026 14:01

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 = 0 now happens on abort — createEntryStream registers 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 own untrack abort marks the entry stale; update is a plain writeEntryData again and the write-time abort branch is gone.
  • @pinia/colada floor raised to 1.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 asserts signals[1] aborted plus entry().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.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

@dinwwwh
dinwwwh force-pushed the claude/lucid-brahmagupta-tfpwfg branch 2 times, most recently from ffdf8cf to 23d077a Compare November 4, 2026 15:27

@pullfrog pullfrog Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ 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) and followEntryStream (pinia) now own data writes and stale marking, and the new shared runWithOwnSignal gives 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 pipeStreamToCache to drive the iterator manually — a failing iterator.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 update implementations, so an 'append' connect write no longer produces a cache notification.
  • Re-scoped both SSR plugins — TanStack detects the server with environmentManager.isServer() and sets refetchOnMount: false there (the browser keeps 'always'); pinia now applies staleTime: 0 in the browser only.
  • Trimmed the returned streamed array to maxChunks again in both integrations, and SWR's .liveSubscriber now passes the event value instead of a mutator.
  • Tests and docs — new shared iterator/runWithOwnSignal cases, TanStack reopen and unchanged-write cases, pinia reopen cases, and the SSR/staleTime/live-undefined documentation 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.

Comment thread packages/tanstack-query/src/ssr.ts Resolved
…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
@dinwwwh
dinwwwh force-pushed the claude/lucid-brahmagupta-tfpwfg branch from 23d077a to 75a3d73 Compare November 4, 2026 15:40

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 followEntryStream now skips the reopen when the fetch signal is already aborted, and (await follow(undefined)) ?? result keeps phase 1's settled data when the reopened stream writes nothing, so a successful live entry is no longer flipped to status: 'error' by an empty reopen.
  • Added a regression test — live-query.test.tsx simulates 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 server false / 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).

Pullfrog  | View workflow run | Using deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants