client: defaultCallOptions on createClient (0.216.1)#372
Merged
Conversation
Add an optional `defaultCallOptions: CallOptions | (() => CallOptions)`
on `ClientOptions` that's merged into every leaf call (`rpc`, `stream`,
`upload`, `subscribe`). Caller-supplied `options` still win field-by-
field — so a caller can override `signal` while keeping other defaults.
Function-form is resolved per call, so consumers can parameterize a
single client without re-creating it (e.g. an ambient signal that
changes between invocations).
Motivation: consumers like agent-harness's pid2 wrapper had to roll
their own recursive `Proxy` to inject a default `{ signal }` into every
leaf RPC, which was fragile against river's callable intermediate
segments — wrapping at the wrong level silently dropped the signal.
Pushing this into river removes the ad-hoc client wrapping.
Also exports `CallOptions` and `ClientOptions` from the router index so
downstream consumers can reference both types directly.
Bumped to 0.216.1 (patch — additive, no breaking changes).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
darshkpatel
approved these changes
May 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Consumers of river that need to inject a default
signal(or otherCallOptionsfield) into every leaf RPC currently have to wrap the client with a recursiveProxy. That's fragile against river's callable intermediate segments —client.<service>is itself callable, so naive wrappers silently drop the signal at the leaf if the recursion doesn't special-case function-valued targets.What changed
ClientOptionsgains optionaldefaultCallOptions?: CallOptions | (() => CallOptions).createClientdispatch path resolves the default (calling the function form per leaf invocation) and spreads it under the caller'soptions. Caller wins field-by-field.CallOptionsandClientOptionsfrom the router index so consumers can name them.Tests
Three new E2E cases under the existing transport×codec matrix (12 runs total):
defaultCallOptions provides signal when caller omits it— eager form drives cancellation.caller-supplied signal overrides defaultCallOptions— verifies caller wins.function-form defaultCallOptions is resolved per call— two subscribes capture different signals from a closure variable; aborting one signal cancels only its subscribe.Full suite: 661 passed | 1 pre-existing skip.
Test plan
npx tsc --noEmitnpx eslint .npx prettier . --checknpx vitest run(full suite, 661/662)Revertibility
One commit. The new field is optional; existing call sites are unaffected.
🤖 Generated with Claude Code