Skip to content

fix(tanstack-query,pinia-colada): let object overrides beat function base modifiers in query utils - #2213

Merged
dinwwwh merged 6 commits into
mainfrom
claude/beautiful-curie-l2rwsk
Oct 13, 2026
Merged

dinwwwh merged 6 commits into
mainfrom
claude/beautiful-curie-l2rwsk

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

Problem

mergeProcedureUtilsModifier in @orpc/tanstack-query and @orpc/pinia-colada combines a base and an override modifier (queryOptions, queryKey, mutationOptions, ...). When the base was a function and the override a plain object, the override was applied first and the base function last. So every key the base set beat the override:

const merged = mergeProcedureUtilsOptions(
  { queryOptions: o => ({ staleTime: 1000, ...o }) },
  { queryOptions: { staleTime: 0 } },
)
merged.queryOptions({}) // { staleTime: 1000 }, expected { staleTime: 0 }

With two objects the override wins, so which side won depended on how the lower layer was written. End-user example: a function queryOptions in contract meta (tanstackQuery / piniaColada) beat an object queryOptions set on the utils through ContractOptionsUtilsPlugin. The docs say utils options merge on top of meta.

Fix

When the base is a function and the override is an object, the override is now passed to the base function as defaults under the per-call options:

return options => base({ ...override, ...options })

This gives per-call options > override > base defaults for object values. A function modifier's result still wins over the options it receives, the same as it already does over per-call options. The other three combinations behave as before. The mergeProcedureUtilsOptions JSDoc now states this rule.

Tests

  • procedure-utils.test.ts (both packages): function base + object override on the same key. The override wins, and a per-call value still wins over both.
  • meta.test.ts (both packages): the same case end to end through createRouterUtils + ContractOptionsUtilsPlugin.

Intentionally unchanged

This PR does not change how an object base combines with a function override. The override function receives the per-call options merged with the base object, so the position of its ...options spread decides priority:

  • o => ({ staleTime: 0, ...o }) keeps a lower layer's staleTime.
  • o => ({ ...o, staleTime: 0 }) forces 0.

Because the function sees the base values, it can also wrap them, for example a meta onSuccess callback. A commit on this branch briefly changed this behavior and has been reverted.

🤖 Generated with Claude Code

https://claude.ai/code/session_01B5V7qjA3f5ancGzD2FDQEF

When the base modifier was a function and the override a plain object,
`mergeProcedureUtilsModifier` applied the override first and the base
function last, so every key the base set beat the override. With two
objects the override won, so priority depended on how the lower layer
was written (e.g. a function `queryOptions` in contract meta beat an
object `queryOptions` on the utils).

Now an object override is fed to a function base as defaults under the
per-call options, giving per-call options > override > base defaults.
Applied to both @orpc/tanstack-query and @orpc/pinia-colada.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B5V7qjA3f5ancGzD2FDQEF
State the precedence rule instead of listing each branch of the private
merge helper, and note that a function override receives the result of
the base.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B5V7qjA3f5ancGzD2FDQEF
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 13, 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 3be71cf Commit Preview URL

Branch Preview URL
Oct 13 2026, 08:03 AM

@pkg-pr-new

pkg-pr-new Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
More templates

@orpc/ai-sdk

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

@orpc/arktype

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

@orpc/bun

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

@orpc/client

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

@orpc/cloudflare

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

@orpc/contract

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

@orpc/experimental-effect

npm i https://pkg.pr.new/@orpc/experimental-effect@2213

@orpc/evlog

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

@orpc/hibernation

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

@orpc/json-schema

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

@orpc/experimental-lock

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

@orpc/experimental-msw

npm i https://pkg.pr.new/@orpc/experimental-msw@2213

@orpc/nest

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

@orpc/next

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

@orpc/node

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

@orpc/openapi

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

@orpc/opentelemetry

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

@orpc/pinia-colada

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

@orpc/pino

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

@orpc/publisher

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

@orpc/ratelimit

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

@orpc/server

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

@orpc/shared

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

@orpc/swr

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

@orpc/tanstack-query

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

@orpc/trpc

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

@orpc/valibot

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

@orpc/zod

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

commit: 3be71cf

@codspeed

codspeed Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

⚠️ 5 benchmarks spent significant time in system calls

System calls cannot be consistently instrumented, so they are not included in the measure, which understates the real cost. Please switch to the Walltime instrument to accurately measure system calls.

Measurement and system calls

✅ 28 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing claude/beautiful-curie-l2rwsk (3be71cf) with main (0dd6994)

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.

ℹ️ Minor suggestion inline.

Reviewed changes

  • Function-base + object-override precedence — mergeProcedureUtilsModifier now passes the object override into a function base as defaults (base({ ...override, ...options })), so override values beat the base function's defaults while per-call options still win; the other three base/override combinations are unchanged.
  • Composed-modifier branch refactor — drops the applyOverride helper and calls override(applyBase(options)) directly.
  • Precedence JSDoc — mergeProcedureUtilsOptions doc rewritten to describe the new ordering.
  • Regression tests — new procedure-utils.test.ts and meta.test.ts cases in both @orpc/tanstack-query and @orpc/pinia-colada cover function meta + object utils end to end.

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

Comment thread packages/tanstack-query/src/procedure-utils.ts Outdated
@dinwwwh dinwwwh changed the title fix: let object overrides beat function base modifiers in query utils fix(tanstack-query,pinia-colada): let object overrides beat function base modifiers in query utils Oct 13, 2026
…ns JSDoc

The precedence "per-call options beat override, which beats base" only
holds for object values: a function modifier receives the merged
options and its result wins, even over per-call options.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B5V7qjA3f5ancGzD2FDQEF
@codecov

codecov Bot commented Oct 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

With an object base and a function override, the override received the
base values merged into its input, so it could not tell them from
per-call options and a defaults-style function (`o => ({ x: d, ...o })`
or `o.x ?? d`) kept the base value. E.g. an object `queryOptions` in
contract meta beat a function `queryOptions` on the utils.

The override now receives only the per-call options, and the object base
fills the keys missing from its result: per-call options > override >
base. Two functions are still composed (base applied first).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B5V7qjA3f5ancGzD2FDQEF
@dinwwwh dinwwwh changed the title fix(tanstack-query,pinia-colada): let object overrides beat function base modifiers in query utils fix(tanstack-query,pinia-colada): let override modifiers win when mixing object and function modifiers Oct 13, 2026
This reverts commit edf4c0c. A function override modifier again receives
the per-call options merged with an object base, so the position of its
`...options` spread decides priority: `{ x: d, ...options }` keeps lower
layers' values, `{ ...options, x: d }` forces `d`. It also keeps
function overrides able to wrap base values such as callbacks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B5V7qjA3f5ancGzD2FDQEF

@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

Since the prior pullfrog review (2fd9a42), this run reviewed the 31e9b77 → edf4c0c delta:

  • Object base + function override precedence — mergeProcedureUtilsModifier now returns options => ({ ...base, ...override(options) }), so the override function receives only the per-call options and its result is spread over the object base, which fills any keys the override omits. Previously this case composed as override(applyBase(options)), letting the base value beat a defaults-style override function.
  • applyBase / applyOverride helpers removed — the function + function branch now calls override(base(options)) directly, matching the composition the helpers previously expressed.
  • JSDoc rewrite — the mergeProcedureUtilsOptions doc now states override beats base with objects acting as defaults under per-call options, that a function modifier's result wins over what it receives, and that two functions compose base-first.
  • Regression tests — the object-base + function-override case is added to procedure-utils.test.ts and covered end to end through createRouterUtils + ContractOptionsUtilsPlugin in meta.test.ts, in both @orpc/tanstack-query and @orpc/pinia-colada.

Verified on edf4c0c: vitest run packages/tanstack-query packages/pinia-colada (246 passed), tsc -b on both packages, and eslint --max-warnings=0 on all six changed files are all clean. The one prior inline suggestion (JSDoc overstating precedence for function modifiers) was reworded and its thread is resolved.

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

@dinwwwh dinwwwh changed the title fix(tanstack-query,pinia-colada): let override modifiers win when mixing object and function modifiers fix(tanstack-query,pinia-colada): let object overrides beat function base modifiers in query utils Oct 13, 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

Since the prior pullfrog review (edf4c0c), the branch gained one new commit (3be71cf) that reverts the object-base + function-override change from edf4c0c:

  • Reverted the object-base + function-override branch — mergeProcedureUtilsModifier again composes options => override(applyBase(options)) for an object base with a function override, so the override function receives the object base values under the per-call options — the pre-#2213 behavior the PR body calls "intentionally unchanged". The { ...base, ...override(options) } special case and the applyOverride helper are gone.
  • Removed the matching regression tests — the two per-package tests asserting a function override beats an object base (in procedure-utils.test.ts and meta.test.ts) are dropped; the tests covering the object-override-beats-function-base fix remain.
  • Restored the narrower precedence JSDoc — the mergeProcedureUtilsOptions doc now states that, for object values, per-call options beat override which beats base, and that a function modifier receives the merged options and its result wins — accurate for the current code.

Verified on 3be71cf: vitest run packages/tanstack-query packages/pinia-colada (242 passed), tsc -b on both packages, and eslint --max-warnings=0 on all six changed files are clean. The prior inline thread (JSDoc overstatement) remains resolved by the current wording.

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

dinwwwh commented Oct 13, 2026

Copy link
Copy Markdown
Member Author

CodSpeed Performance Analysis failed on 3be71cf: range request in benches/static-file-handler.bench.ts reported -10.69% (1 ms → 1.1 ms). This is not caused by this PR:

  • Code not touched: this PR only changes procedure-utils.ts (and tests) in @orpc/tanstack-query and @orpc/pinia-colada. The static file handler benchmark doesn't use either.
  • Same code passed before: 3be71cf is a revert, and its files are identical to 31e9b77. CodSpeed reported "will not alter performance" for 31e9b77.
  • CodSpeed's own warnings: it flags this benchmark as spending significant time in system calls, which it doesn't measure, and reports "Different runtime environments detected" for this comparison.

There's no code fix to port. I'm re-running the CodSpeed workflow once. If the result is still red, a maintainer can acknowledge it on CodSpeed.


Generated by Claude Code

@dinwwwh
dinwwwh merged commit 10e2df9 into main Oct 13, 2026
13 checks passed
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.

2 participants