Description
When using newWebSocketRpcSession or newHttpBatchRpcSession with a TypeScript generic parameter, the RpcStub<T> type preserves primitive return types (string, number, boolean) but fails to preserve object/interface return types, returning any instead.
Minimal Reproducible Example
import { newWebSocketRpcSession, type RpcStub } from 'capnweb';
// Define types
type ApiMessage = { message: string; timestamp: string };
// Define API interface
interface MyApi {
getPrimitive(): Promise<string>; // Returns primitive
getObject(): Promise<ApiMessage>; // Returns object
hello(name: string): Promise<ApiMessage>; // Returns object
}
// Create client
const apiStub = newWebSocketRpcSession<MyApi>('wss://example.com/api');
// Primitives work correctly ✅
const primitive = await apiStub.getPrimitive();
// ^? Type is 'string' ✅
// Objects return 'any' ❌
const object = await apiStub.getObject();
// ^? Type is 'any' ❌
const helloResult = await apiStub.hello('world');
// ^? Type is 'any' ❌
Expected Behavior
All method return types should be preserved, regardless of whether they're primitives or objects:
const primitive = await apiStub.getPrimitive();
// ^? string ✅
const object = await apiStub.getObject();
// ^? Should be: ApiMessage
const helloResult = await apiStub.hello('world');
// ^? Should be: ApiMessage
Actual Behavior
Only primitive return types are preserved. Object/interface return types become any:
const apiStub: RpcStub<MyApi> = newWebSocketRpcSession<MyApi>('wss://example.com/api');
// ^? Correctly shows: RpcStub<MyApi>
const primitive = await apiStub.getPrimitive();
// ^? string ✅ Works!
const object = await apiStub.getObject();
// ^? any ❌ Should be ApiMessage
const helloResult = await apiStub.hello('world');
// ^? any ❌ Should be ApiMessage
// No type checking for objects
console.log(helloResult.anythingGoes); // No error
Root Cause
The RpcStub<T> type definition appears to handle primitives correctly but wraps object return types in complex proxy types (for promise pipelining support) that resolve to any instead of the original type.
Workaround
Manually cast each result:
const helloResult = await apiStub.hello('world') as ApiMessage;
// ^? Now correctly typed as: ApiMessage ✅
Environment
- capnweb version: 0.2.0
- TypeScript version: 5.8.3
- Node version: 22.15.1
Description
When using
newWebSocketRpcSessionornewHttpBatchRpcSessionwith a TypeScript generic parameter, theRpcStub<T>type preserves primitive return types (string, number, boolean) but fails to preserve object/interface return types, returninganyinstead.Minimal Reproducible Example
Expected Behavior
All method return types should be preserved, regardless of whether they're primitives or objects:
Actual Behavior
Only primitive return types are preserved. Object/interface return types become
any:Root Cause
The
RpcStub<T>type definition appears to handle primitives correctly but wraps object return types in complex proxy types (for promise pipelining support) that resolve toanyinstead of the original type.Workaround
Manually cast each result:
Environment