Repository navigation
fix(tanstack-query,pinia-colada): let object overrides beat function base modifiers in query utils - #2213
Conversation
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
Deploying with
|
| 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 |
More templates
@orpc/ai-sdk
@orpc/arktype
@orpc/bun
@orpc/client
@orpc/cloudflare
@orpc/contract
@orpc/experimental-effect
@orpc/evlog
@orpc/hibernation
@orpc/json-schema
@orpc/experimental-lock
@orpc/experimental-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: |
Merging this PR will not alter performance
|
There was a problem hiding this comment.
ℹ️ Minor suggestion inline.
Reviewed changes
- Function-base + object-override precedence —
mergeProcedureUtilsModifiernow 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
applyOverridehelper and callsoverride(applyBase(options))directly. - Precedence JSDoc —
mergeProcedureUtilsOptionsdoc rewritten to describe the new ordering. - Regression tests — new
procedure-utils.test.tsandmeta.test.tscases in both@orpc/tanstack-queryand@orpc/pinia-coladacover function meta + object utils end to end.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
…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 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
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
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Since the prior pullfrog review (2fd9a42), this run reviewed the 31e9b77 → edf4c0c delta:
- Object base + function override precedence —
mergeProcedureUtilsModifiernow returnsoptions => ({ ...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 asoverride(applyBase(options)), letting the base value beat a defaults-style override function. applyBase/applyOverridehelpers removed — the function + function branch now callsoverride(base(options))directly, matching the composition the helpers previously expressed.- JSDoc rewrite — the
mergeProcedureUtilsOptionsdoc now statesoverridebeatsbasewith 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.tsand covered end to end throughcreateRouterUtils+ContractOptionsUtilsPlugininmeta.test.ts, in both@orpc/tanstack-queryand@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.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ 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 —
mergeProcedureUtilsModifieragain composesoptions => override(applyBase(options))for an objectbasewith a functionoverride, 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 theapplyOverridehelper are gone. - Removed the matching regression tests — the two per-package tests asserting a function override beats an object base (in
procedure-utils.test.tsandmeta.test.ts) are dropped; the tests covering the object-override-beats-function-base fix remain. - Restored the narrower precedence JSDoc — the
mergeProcedureUtilsOptionsdoc now states that, for object values, per-call options beatoverridewhich beatsbase, 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.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
|
CodSpeed Performance Analysis failed on
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 |

Problem
mergeProcedureUtilsModifierin@orpc/tanstack-queryand@orpc/pinia-coladacombines 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:With two objects the override wins, so which side won depended on how the lower layer was written. End-user example: a function
queryOptionsin contract meta (tanstackQuery/piniaColada) beat an objectqueryOptionsset on the utils throughContractOptionsUtilsPlugin. 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:
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
mergeProcedureUtilsOptionsJSDoc 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 throughcreateRouterUtils+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
...optionsspread decides priority:o => ({ staleTime: 0, ...o })keeps a lower layer'sstaleTime.o => ({ ...o, staleTime: 0 })forces0.Because the function sees the base values, it can also wrap them, for example a meta
onSuccesscallback. A commit on this branch briefly changed this behavior and has been reverted.🤖 Generated with Claude Code
https://claude.ai/code/session_01B5V7qjA3f5ancGzD2FDQEF